955 Commits
Author SHA1 Message Date
e039ad26c5 [editor-core] extract logic for determining root doc from snapshot (#37043)
* [editor-core] hold the one threshold that decides which store owns the file tree

HISTORY_FILE_TREE_STAGE is five literals across the services that consume this
library -- web backend, web editor, real-time, third-party-datastore,
github-sync -- linked by comments saying which other copies they are aligned
with. What the docstring here says is what goes wrong: this is the single thing
that decides which storage is authoritative, a project crosses it once with no
way back, and a copy that drifts sends a whole service's writes to the store
nothing reads any more. Silently, and for good.

overleaf-editor-core is where it goes: every one of the five already depends on
it, the editor included, and the library that defines what a file tree is can say
where one lives. The consumers adopt it separately; this is the definition they
adopt.

historyIsSourceOfTruth goes with it, and refuses a stage that is neither absent
nor a number rather than reading it as low -- web's copy coerced, which turned a
corrupt stage into a permanent mongo write. Absent stays "below the gate": a
project that never migrated has no stage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] hold which doc a project opens on

History records it as `main: true` on the file, and nothing derives it: every
writer that creates a project has to record it, or the project compiles nothing
until the user picks a file. So the rule for choosing one, and the operations
that record it, go where a file tree is defined rather than with any one writer.

The rule is the one web has always applied, over a snapshot instead of a mongo
tree: of the docs that declare a document class, the one nearest the project
root, alphabetically among equals, so that the same files always give the same
answer. A project that already records a root doc keeps it -- that is a user's
choice and not something to re-derive.

Whether a doc could be the one is asked of content rather than of a store to read
it from: a writer creating a project has each doc's content in its hands as it
stores it, so reading them back would be a second read of every one. The choice
is then made from the pathnames alone.

The extension list joins the others in text_file_defaults for the reason they are
there: a writer holding a shorter copy leaves a project with nothing to compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] say why a store built for a consumer does not need getBlob

ReadWriteBlobStore is BlobStore without the lookup by hash, and the type said so
without saying why a consumer can do without it. getBlob is reached only for a
file stored as a bare hash, and one of those does not leave history-v1:
chunk_store's lazyLoadHistoryFiles attaches blob metadata to every snapshot and
change it serves, so a file arrives already knowing its own lengths.

That is what the contract rests on, and a store that reimplemented the
classification rule to answer the lookup anyway would be a second opinion on
whether a file is editable. The reasoning was what was missing, rather than the
type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] say what makes a doc a root doc candidate

The sentence naming the two halves of the check had lost the first one, and read
"named as a root doc may be named". It is the extension that is tested: one of
DEFAULT_ROOT_DOC_EXTENSIONS, and a document class declared in the content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] hold the doc flags a file's metadata can carry in one place

`main` is not the only flag a doc carries -- `mainBibliography` marks the file
references are added to -- and both live in the same metadata, which
SetFileMetadataOperation replaces wholesale rather than merges. So every writer
that sets one has to write the others out again, and every reader that asks
whether a file is a doc has to know the whole list.

root_doc was spelling all three of those for itself against `main` alone: it
tested the key by hand, refused a file whose metadata held anything else, and
cleared the metadata to `{}` when moving the root doc elsewhere. Against a file
that is also the main bibliography, the first two refuse a legitimate root doc
and the third silently drops the bibliography.

The helpers answer it once: which keys are doc flags, whether metadata holds only
those, and what metadata with one flag changed looks like. root_doc uses them
throughout, and the schema takes both flags on one file.

Co-Authored-By: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] ask the snapshot which doc carries a flag

Finding the file a doc flag is set on is the same search whichever flag is being
asked about, and it belongs to the snapshot rather than to the caller: it walks
the snapshot's own pathnames, and it is the snapshot that knows a file's
metadata without its content being loaded.

root_doc's mainPathname is that search for `main`, and now delegates. It gains
the check it was missing: a file whose metadata holds anything besides doc flags
came from outside the editor, so a stale `main` on it does not make it the root
doc -- which is already what setMainPathnameOperations refuses to write.

Co-Authored-By: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] drop mainPathname for the snapshot method behind it

It became a second name for `snapshot.getPathnameWithDocFlag('main')` and nothing
in the library was left calling it but chooseRootDoc, which asks the snapshot
directly now. Its two tests go with it: Snapshot's own cover the same two answers,
and more of them.

Callers outside the library ask the snapshot too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mathias Jakobsen <mathias.jakobsen@overleaf.com>
GitOrigin-RevId: b3f503aa3e94ea8b6524d83f910fe33dc381c0a5
2026-09-17 12:08:31 +00:00
Jakob Ackermann 37e27bb22e [saas-e2e] reduce disk IO and webpack overhead (#36997)
* [monorepo] reduce writes from chrome in cypress

Chrome puts its shared memory in /dev/shm, and docker's default 64MB is far
too small for it - which is why Cypress passes --disable-dev-shm-usage,
spilling those segments to files on the container's writable layer instead.
Under one spec that is a few hundred MB of write-then-delete churn; under the
CI fan-out it was most of the disk bandwidth on the whole box.

So every service built from dockerfiles/cypress gets shm_size 1gb and every
cypress config drops the flag again: the SaaS e2e service, web's CI and local
component-test services plus its standalone cypress one, and server-ce's e2e
and native cypress services. Both e2e-open variants inherit the setting
through `extends`; the native one is a service of its own, and would be left
running Chrome against 64MB once the flag is gone.

* [monorepo] adopt cypress-pnp-reporter

Materializing the reporter packages copied 267 packages / 3779 files
(59MB) into /tmp on every container start, once per spec. Resolving them
through PnP inside Cypress's Electron process instead drops that to
zero: the entrypoint shell went from 55.6MB of write_bytes to 0.

* [web] serve built assets from a prepared buffer between builds

Serving a chunk cost ~2ms whatever its size, and an editor page pulls ~144
of them, so webpack-dev-server burned 2-3 cores through an e2e run purely
answering requests for output that had not changed since the last build.

It is not bytes: cutting the bundle 2.6x with a cheaper devtool moved CPU
by 8%, and an empty 304 cost nearly as much as the full body. It is the
per-request work - reading the asset out of the in-memory filesystem and
re-hashing the whole body for a strong ETag, on every request.

Three things a build does that this has to respect, each found by tracing
the hooks rather than by reading them:

- `done` fires twice per build, for the compilation and for the manifest,
  both with the same hash. A flag would have been cleared by the second
  while the first was still running, so the gate is a counter.
- An incremental `done` reports only the assets it rewrote - 16, not 630 -
  so asset names accumulate. Resetting them on `invalid` left the
  manifest's two behind and nothing was served from here again.
- webpack-dev-middleware holds requests during a rebuild. This sits in
  front of it, so it steps aside for that window and drops the cache when
  a rebuild starts. Whether a response can be kept is then decided as it
  ends rather than as it began: an epoch bumped on `invalid` names the
  build its bytes were read from, and only a response from the current
  build with no rebuild in flight is stored. A rebuild starting
  mid-response can no longer write the previous build's bytes in behind
  the cache being dropped, which eviction by asset name could not catch
  anyway - an incremental build does not name the assets it left alone.

Measured on the editor entrypoint, 16 concurrent clients:
0.26-0.31s -> 0.045-0.050s of webpack CPU per page load, and 0.0455s
immediately after a rebuild. Same chunk graph, byte-identical output.

GitOrigin-RevId: 08b4ab2d665414069bc01b4763dd64faa9b4e5cb
2026-09-17 12:07:28 +00:00
Jakob AckermannandClaude Opus 5 04871d6049 [editor-core] extract more shared utils (#36954)
* [editor-core] share the blob hash and the doc-vs-file rule

The git blob hash and the rule that decides whether a file is a doc or a binary
file both have to reach the same answer in every service that writes to history,
so they move into overleaf-editor-core rather than being spelled out again on
each side.

blob_utils takes over lib/blob_string_length.js: hashing and the string-length
rule answered two halves of the same question -- what history needs to know
about content before it can store it -- and each carried the same warning about
staying off index.js, which is bundled for the browser. One module states it
once, and blobForFile joins them, describing content on disk as the blob it
amounts to. The hashing functions are flat rather than a namespace of their own:
in a module that also holds the string-length rule, a bare fromStream says
nothing about what it makes. history-v1's blob store and recover_zip follow the
module to its new name.

file_type_detector holds the doc-vs-file rule, Node-only and deep-imported for
the same reason. It takes textExtensions, editableFilenames and maxDocLength as
configuration rather than reading settings: the limit is web's 2 MiB and not
editor-core's own 3 MiB, and the surrogate check covers both halves of a pair
where editor-core's covers the high half.

buildSetContentOperations' test spelled the blob header out for itself, with the
separator as a literal NUL byte, which had git reading the file as binary. It
hashes through blobHashFromString now, so it does not spell the header at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] name the client a change came from on the origin itself

`EditorOrigin` carried an `editorId` that only an editor change could have. What
the id is for -- recognising a change again, so history does not apply a resend
twice and the client does not apply its own change back -- is a property of the
client that submitted it rather than of what it did, so it moves onto `Origin` as
an optional `historyClientId` and `EditorOrigin` goes.

- `Origin` takes `(kind, historyClientId)` and answers `getHistoryClientId()`;
  `dropHistoryClientId()` is what the chunk store uses to strip the id from all
  but a client's latest change, which now leaves the rest of the origin alone --
  a restore that a client submitted stays a restore.
- `toRaw` is built a layer at a time: the base writes the kind and the id, a
  subclass spreads that and adds the fields it owns.
- The named kinds are their own schemas and discriminate on `kind`, so a payload
  claiming one is checked against that shape and the error says what it got
  wrong. The catch-all cannot join them -- its kind is any string -- so it sits
  alongside and is refined to refuse the named kinds, which it used to swallow:
  `{kind: 'restore'}` validated as a kind and nothing else, then read back as a
  restore that recorded nothing about what it restored.
- `lib/origin/*.js` gains `// @ts-check`. Without it the annotations in there
  were decorative: the lazily required subclasses were `any`, so nothing handed
  to them was checked, and `fromRaw` narrows with `in` to drop the catch-all
  variant, which a kind check cannot.

The callers that stamp the id -- real-time's handshake flag, web's applyChange
body -- come with the rest of the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] recognise a change a writer that is not an editor placed

The counterpart of `isSameEditorChange` for writers that are not an editor
client: an integration that commits a batch on a user's behalf has to recognise
that batch coming back, because a lost response does not say whether the commit
landed and resubmitting one that did applies its removes twice.

It compares the same three fields, and for the same reason -- a rebase rewrites a
change's operations but leaves its origin, authors and timestamp alone -- with
the timestamp compared as a point in time rather than as a string, so that two
spellings of the same instant cannot read as different changes. github-sync is
the first caller, and comes with the rest of the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] load an editable file's content, or say it has none

Only an editable file has content: any other kind is bytes addressed by hash, and
hollow data records the size of content it does not have. Both answer
`getContent()` with null, so every caller that wants the content of a doc loaded
it, checked the data's class, and reached for its own error when the check failed.
`File#loadEager` does that once and answers the data that always has the content.

`buildSetContentOperations` takes a `ReadWriteBlobStore` -- everything but
looking a blob up by hash, which is all it takes to build operations over content
-- so a caller holding a store that cannot answer `getBlob` can still use it. Its
metadata argument is `FileMetadata` rather than `Object`, which is what the schema
already says a file's metadata is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [editor-core] say that a pair of operations that do not fit is unprocessable

compose and transform threw a bare Error when the two operations do not describe
the same content. Everything that answers a client for these -- history-v1's
import routes, the git-bridge push, project-history's resync -- decides on
UnprocessableError, so a bare Error reached them as an internal failure: a 500,
which reads as worth retrying, for a pair that fails the same way every time.
A client that resubmits on a 500 resubmits forever.

The guards that cannot be reached through the API keep their bare Error: a
TextOperation composed with something that is not one, and the branch whose
message says it should not happen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [history-v1] hash blobs with the rule editor-core holds

storage/lib/blob_hash.js was a bluebird-era implementation of the same git blob
hash editor-core holds, exported from the storage barrel and used across the
storage layer, the api and the tests. All of it reads the shared rule now, and
the test that existed only to check the copy is gone -- editor-core's suite
covers the same non-ASCII case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 5a0c1e9a06170a0164c01a26a7bb2d205726d531
2026-09-17 12:07:15 +00:00
Jakob Ackermann 2317fd6edb [web] remove generic analytics service proxy (#36934)
GitOrigin-RevId: 19b3613b7cc84e73ad556067da1b3cd2f956e1c5
2026-09-17 12:06:50 +00:00
Jakob AckermannandClaude Opus 5 6caa5fc3a3 [monorepo] read empty content from its hash, in a shared blob store base (#36831)
The hash of empty content is a property of the content rather than of anything
stored: every project's empty file has it, whether or not a blob was ever written
under it, and a file may reference it before anything has. Eight blob stores were
each paying a lookup -- a database query, an HTTP request, a read of a cache that
offline mode may be the only thing standing in for -- to be told what the hash
already says, and clsi had already reinvented the answer on its own.

overleaf-editor-core gains BlobStoreBase, which answers getString for that hash
and delegates the rest to the fetchString its subclasses provide. getObject comes
with it: four stores spelled out the same JSON.parse of getString, and a fifth
wrapped it in a log line.

Adopted by history-v1's BlobStore, BackupBlobStore, HashCheckBlobStore and
recover_zip's RecoveryBlobStore, by project-history's BlobStore, by clsi's
BlobStore, and by web's HistoryBlobStore, SimpleBlobStore and FrontendBlobStore.
history-v1 keeps its own answers in getStream, getBlob and getBlobs, which no
other store implements. Writing and copying a blob are untouched, so a project
that wrote empty content still has a blob for it and a read answers the same
either way.

Two paths could not handle a project holding a tracked change or a comment at
all. Loading such a file needs the ranges blob its rangesHash points at, and
hashing one again needs to write it back, so a store missing getObject or
putObject fails outright:

- recover_zip died on `blobStore.getObject is not a function`, making every such
  project unrecoverable. The test covering it asserts a tracked delete is absent
  from the zip, which the script already asked for and never reached.
- GET /projects/:project_id/latest/hashed_content returned 500 from the same
  cause, then would have hit a missing putObject in snapshot.store.
  HashCheckBlobStore becomes a class over the base and gains putObject.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
GitOrigin-RevId: c84f62465beff6cf77ffac26aa772984e1b554cb
2026-09-17 12:06:30 +00:00
Jakob AckermannandClaude Opus 5 7ae8db6b12 [editor-core] extract shared pieces for history-ot in editor (#36828)
* [editor-core] share the pieces the editor, real-time and history-v1 all need

Project-level history-OT has the browser, real-time and history-v1 all reason
about the same changes, so the rules they reason with move into the shared
library rather than being reimplemented on each side.

- `origin/editor_origin.js` plus `EDITOR_ORIGIN_KIND` and its schema entry: the
  origin an editor client's change carries, holding the editorId that scopes a
  change's timestamp.
- `change_identity.js`: how a submission is recognised when it comes back, so a
  resend of a change that already landed is deduplicated rather than applied
  twice.
- `rebase.js`: transforming a change onto concurrent ones, in the same order on
  both sides, which is what lets the server acknowledge a change with only the
  version it landed at.
- `text_file_defaults.js`: the doc-vs-file extension lists, now read from here
  by web's settings instead of being spelled out again.
- `blob_string_length.js`: the string length of a blob, used by history-v1's
  blob store and by web when it writes an uploaded file into history.
- `file_tree_diff.js`: file-tree operations to a diff, used by
  project-history's FileTreeDiffGenerator.
- `Blob.EMPTY_HASH`, so callers can answer for the empty blob without storing
  it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [project-history] build the file-tree diff with the shared helper

FileTreeDiffGenerator's hand-rolled walk over add/edit/move/delete operations
becomes a call to `buildFileTreeDiff` from overleaf-editor-core, so the editor
and project-history read a file-tree change the same way. Adds the unit tests
the decaffeinated original never had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [web] read the editable-file lists from overleaf-editor-core

web's settings spelled out the text extensions and editable filenames that
decide whether a file becomes a doc, and every other service that has to make
that call carried its own copy. The lists now live in overleaf-editor-core and
web builds settings.textExtensions and settings.editableFilenames from them,
keeping the ADDITIONAL_TEXT_EXTENSIONS override for Server CE/Pro on top.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* [history-v1] read a blob's string length from overleaf-editor-core

Two copies of getStringLengthOfFile lived here, the one in recover_zip.js
carrying a TODO asking for exactly this. Both now import the shared
implementation, which the blob store keeps re-exporting for backupArchiver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
GitOrigin-RevId: 31948332f416f56848e3b0dfb5142a4c9f93f508
2026-09-17 12:06:17 +00:00
Eric Mc Sween 2024f85941 Merge pull request #36842 from overleaf/em-tpds-typescript
Convert third-party-datastore to TypeScript

GitOrigin-RevId: 028172063d0cfe03c0f105f48ab66020c79ea50e
2026-09-17 12:06:14 +00:00
Malik Glossop 863e33af78 Merge pull request #35982 from overleaf/mg-history-ot-comments
HistoryOT - Fix comments being dropped and crashing on detached ranges

GitOrigin-RevId: 0906a55a51a6fda9ac828efcbe72989573161261
2026-09-17 12:03:46 +00:00
Jakob Ackermann 9b44fd075b [monorepo] downgrade ranges ids back to strings for now (#36857)
GitOrigin-RevId: 6c85715bc85adc2f641bfd0e9f3bb6a8f2c839c2
2026-09-17 12:03:33 +00:00
Jakob AckermannandClaude Opus 5 91bf8f13ba [validation-tools] add a throw+log mode to parseReq (#36821)
REQ_VALIDATION_MODE=enforce-log rejects schema failures exactly as 'enforce'
does, but logs them first at instrumented call sites, so the visibility built
for the 'log' rollout (sanitized issues, caller trace, logFields values, per
schema/signature dedup) survives moving out of 'log' mode.

opts no longer change the outcome in this mode: logOnly does not suppress the
throw, the error is classified from the primary schema (not the fallback), and
fallbackSchema is only re-parsed to tag the log entry -- 'enforced-fallback-
passed' marks a rejection the looser pre-refinement schema would have avoided,
i.e. one the tightened schema newly introduces.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 0743322990e6c570ffbae166b4586285a18221df
2026-09-17 12:03:25 +00:00
Anna Claire Fields 285bcd4315 [editor-core] accept the legacy source_project_display_name on project_file (#36837)
GitOrigin-RevId: bebbba328ecdd04ac4df06f66e6b0b1f8a41276f
2026-09-17 12:03:05 +00:00
Jakob Ackermann 4e80d65efc [ranges-tracker] add legacy orderedRejections field in tracked-changes (#36565)
* [docstore] use more realistic test stubs for ranges

* [monorepo] define ranges schemas in @overleaf/ranges-tracker

* [ranges-tracker] add legacy orderedRejections field in tracked-changes

* [monorepo] resolve @overleaf/ranges-tracker subpath imports in typescript

* [real-time] validate comment thread ids and tracked change id seeds

* [ranges-tracker] require ObjectId ids and tracked change metadata in ranges

* [monorepo] require ObjectId tracked change ids on the accept routes

* [web] use ObjectId comment thread ids in the DocumentController tests

GitOrigin-RevId: 58b20dec311d85b917ef63f9efbe745a265a2de6
2026-09-17 12:01:19 +00:00
Eric Mc Sween 833e44d142 Merge pull request #36733 from overleaf/dk-fetch-utils-unset-headers
[fetch-utils] Drop headers with unset values

GitOrigin-RevId: 284b8551dfc3b15315a873a9256fbf9f35e4004b
2026-09-17 12:01:03 +00:00
Andrew Rumble ba4e6ef8ed Merge pull request #35468 from overleaf/ar-fetch-utils-type-improvements
[fetch-utils] type improvements

GitOrigin-RevId: 4652054c715c4ddb19bac1f46cdf1fc3916c533d
2026-09-17 11:59:06 +00:00
Andrew Rumble 44753a2a50 Merge pull request #36606 from overleaf/ar/log-relativePath
[web] log relativePath in uploadFiles endpoint

GitOrigin-RevId: d30cace4632c6c1b70aad2c84a2a6f470ef2ffa8
2026-09-17 11:58:39 +00:00
Brian Gough a4b9b18109 Merge pull request #36402 from overleaf/bg-zod-move-safe-path-tests
move safe path tests from zodHelpers to web

GitOrigin-RevId: 3bb25fa108a4a0da0bed1ae60fabfdd47ed76283
2026-09-17 11:58:29 +00:00
Brian Gough 3e0a54c917 Merge pull request #36568 from overleaf/bg-add-ranges-hash-to-add-file-operation-find-blob-hashes
include range hashes in findBlobHashes for AddFileOperation

GitOrigin-RevId: 5d31f7efbe409b8c0433607152f66262f99cb94c
2026-09-17 11:58:11 +00:00
Anna Claire Fields fd0e0ac7df Log zipUrl value in req-validation schema failure (#36603)
GitOrigin-RevId: c53b1d54e356eb937c12a3d06f620bd515af0042
2026-09-17 11:57:42 +00:00
Jakob Ackermann cd7ac6f2fc [editor-core] gracefully handle verbose project_output_file details (#36552)
GitOrigin-RevId: 2c549db61dc05c02729bf80c8fbd3f31d52f1e9c
2026-09-17 11:56:28 +00:00
Jakob Ackermann de0d1a7226 Merge pull request #36219 from overleaf/jpa-zod-web-platform
[web] platform: adopt strict zod schema and extend test coverage

GitOrigin-RevId: e3a0b7b4c52ac116f28be0d284284e3bd3994cda
2026-09-17 11:55:40 +00:00
Brian Gough 6a74e50ba1 Merge pull request #36388 from overleaf/lg-tar-profiler-upgrade
[Security upgrade] remove tar 6.2.1 and bump tar to 7.5.22 in root workspace

GitOrigin-RevId: 7471e82d64bf0a951a25586c19027300ecdeee98
2026-09-17 11:54:18 +00:00
Brian Gough 3e8f2ebe31 update zod safePath validation to match existing behaviour in web (#36357)
* update zod safePath validation to match existing behaviour in web

* test SafePath from web against zod validation

GitOrigin-RevId: a159f5078b5bf75eac4365c97e34e21e716290eb
2026-09-17 11:51:30 +00:00
Jakob Ackermann 975cf90357 [editor-core] gracefully handle any legacy v1 import agent (#36354)
GitOrigin-RevId: 186afa70c03e69b3288af66d85a0a3e431780d14
2026-09-17 11:50:47 +00:00
Jakob Ackermann 95e3444a6e [editor-core] refine zod schemas for rawTextOperation and rawFileMetadata (#36253)
* [editor-core] gracefully handle no-op TextOperation

* [editor-core] add support for legacy url imports

* [editor-core] reorder rawFileMetadata entries

GitOrigin-RevId: f5c3158def00183d4a60a5b934abd921716e2339
2026-09-17 11:48:47 +00:00
Jakob Ackermann 731ed50f78 [clsi] address zod violations (#36199)
* [editor-core] gracefully handle no-op TextOperation

* [web] avoid sending sync to pdf requests without a ?file

* [validation-tools] log deeper nested violations

* [editor-core] fix round trip of EditNoOperation

* [web] use custom error for sync to pdf w/o file, fix tests

* [validation-tools] include request params in log msg for violation

* Revert "[editor-core] gracefully handle no-op TextOperation"

This reverts commit c1d37261d5db72b37dbf39428c54edcfb5423f41.

* [web] gracefully handle rejected sync to pdf from context menu

GitOrigin-RevId: 21de7dc96b23575b0d67b6eb0c68ed9c2f86292b
2026-09-17 11:48:12 +00:00
Brian GoughandCopilot Autofix powered by AI a9a36237aa Use logger.exit in expire-deleted user/project scripts (#36169)
* add logger.exit method to avoid logs being lost by process.exit

* use logger.exit in expire deleted projects and users scripts

* improve comment for EXIT_DELAY

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* convert to async

* await logger.exit in expire deleted projects and users scripts

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 020d5cc2755871e5343130029706fe3b1c8e15ee
2026-09-17 11:47:35 +00:00
Jakob Ackermann 4ca028cb4c [validation-tools] eventName: accept upper case letters (#36188)
GitOrigin-RevId: 979ec5c744d5304232d447ff5fff076ee081721f
2026-09-17 11:47:25 +00:00
Jakob Ackermann 0776357d1a [document-updater] address zod violations (#36147)
* [editor-core] address zod violation for group_id

* [monorepo] use discriminated union in zod schemas where possible

* [editor-core] v1_source_doc_id is a number

GitOrigin-RevId: 38f591b07cca4e22954bfe30bf855545a6af675d
2026-09-17 11:46:42 +00:00
Anna Claire Fields a63fd554c3 Replace MOCHA_GREP with TEST_NAME_PATTERN (#34404)
GitOrigin-RevId: b1b2d1be3d0725f930d83ef035836482ffb72dcd
2026-09-17 11:45:29 +00:00
Jakob Ackermann 2f69caef63 [editor-core] allow empty ranges on rawAddCommentOperation (#36073)
GitOrigin-RevId: 8674a2b458770439925047aab9a129242eb984d4
2026-09-17 11:44:12 +00:00
Jakob Ackermann f6bb5ce1bd [real-time] adopt strict zod types and extend test coverage (#36031)
* [editor-core] extend coverage of zod schemas

* [real-time] adopt strict zod types and extend test coverage

* [project-history] make type check happy

* [web] make type check happy

GitOrigin-RevId: e1a65b9b3560b31ad2af817067a4b549865a5bb1
2026-09-17 11:42:50 +00:00
Jakob Ackermann 81df324bcc [monorepo] prepare for zod everywhere epic (#36027)
GitOrigin-RevId: f082ae7fc7857fa72b2c1823645050bd4f6df305
2026-09-17 11:42:46 +00:00
Lucie Germain 8a37955106 [Security upgrade] bump @opentelemetry/sdk-node family in libraries/metrics
GitOrigin-RevId: f462289e88c3c021fa32561a96344d76939da4a0
2026-09-17 11:39:57 +00:00
Jakob Ackermann a77a4782cc [history-v1] add setContent endpoint (#35799)
* [history-v1] add setContent endpoint

* [history-v1] refactor setContent to return a change+v

* [history-v1] make typescript happy

GitOrigin-RevId: e2cb0281367c01aa9cd842f250f2016d591ec0fc
2026-09-17 11:39:09 +00:00
Jakob Ackermann 5425202247 [e2e] cut down flakeyness (#35896)
* [saas-e2e] conversion: wait for initial compile before looking at PDF

* [eslint-plugin] s/before/beforeWithReRunOnTestRetry/ in e2e tests

* [server-ce] remove filestore migration scripts and E2E tests

* [server-ce] test: trim down upgrading tests to 5.x -> latest

GitOrigin-RevId: 2426ec4a4f3d67a5e0e7850ea84a942ae4619f94
2026-09-17 11:38:44 +00:00
Jakob Ackermann 94cad14caf [chat] migrate to express with strict zod schema (#35742)
GitOrigin-RevId: edffacde0c069768def849f45364d519d9f76f1e
2026-09-17 11:36:35 +00:00
Malik Glossop 73068f10ee Merge pull request #35571 from overleaf/mg-increase-max-string-size
Bump backend OT/history processing limit (MAX_STRING_LENGTH) to 3MB

GitOrigin-RevId: ed64fe5522c45b1ace221a01daa2ae70939816f9
2026-09-17 11:35:54 +00:00
Andrew Rumble da3adace36 Merge pull request #34850 from overleaf/acf-ar-yarn-pnp-redux
[monorepo] yarn pnp redux

GitOrigin-RevId: fb04cab879b04ead2665b5e985a44053ceea33f3
2026-09-17 11:35:26 +00:00
Mathias Jakobsen d8f167dec2 Merge pull request #35293 from overleaf/mj-zod-history-ot
[real-time] Allow history-ot operations in real-time

GitOrigin-RevId: 4b1b9e4c1a214edf49c5eface46a4d309ba41b0d
2026-09-17 11:31:49 +00:00
Jakob AckermannandAndrew Rumble 9519dc5e66 [fetch-utils] use default agent with a 1s timeout and 3 attempts in connect phase (#35406)
Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: cb1e854d61ce623fa557ae003dc7159b096e1f86
2026-09-17 11:24:37 +00:00
Jakob Ackermann 89f12e6b1f [document-updater] wrap doc locks in project lock (#35416)
* [document-updater] wrap doc locks in project lock

* [document-updater] use BlockingProject as redis key prefix

* [redis-wrapper] update comment

GitOrigin-RevId: 0ce50e76386871e6c29481494febc8d6b37e7df5
2026-09-17 11:24:34 +00:00
Andrew Rumble 05423f1a1d Merge pull request #34344 from overleaf/copilot/convert-fetch-utils-to-es-modules
Convert @overleaf/fetch-utils to TypeScript ESM with Vitest, enable noImplicitAny, fix downstream regressions, and align buildscript generation

GitOrigin-RevId: e060152f056cbae31da0650fb22471525835bd5e
2026-09-17 11:24:05 +00:00
Jakob Ackermann bccef96581 [editor-core] fix TextOperation.isNoop() for tracked-changes (#35389)
* [editor-core] add regression test for composing no-op tracked change

* [editor-core] fix TextOperation.isNoop() for tracked-changes

GitOrigin-RevId: 345ee20275db9bf32ad32037013dde2bda507027
2026-09-17 11:23:53 +00:00
Andrew Rumble 5a602e1b1d Merge pull request #35290 from overleaf/ar-stop-using-unix-formatter-locally
[monorepo] switch from unix to stylish formatter for lint

GitOrigin-RevId: e3c84e556941b4747e469a123a99d5731697976d
2026-09-17 11:21:32 +00:00
Jakob Ackermann 0354f03612 [eslint-plugin] integrate with build scripts (#35176)
GitOrigin-RevId: cc042f70981da3862f2660c695193190b0c62000
2026-09-17 11:19:04 +00:00
Rebeka DekanyandCopilot Autofix powered by AI f30c23cacc Update in-product doc links to point to docs.overleaf.com (#35184)
* update doc links to point to docs.overleaf.com instead of the old learn wiki

* fix broken link to LaTeX positioning wiki article in figure modal help

* point figure positioning help link to the docs.overleaf.com page

* point figure/table label help links to specific anchors on the captioning-and-referencing-figures doc page

* point figure modal error-help link to the graphicx-loading doc page

* normalize target/rel on docs.overleaf.com links opened in a new tab

* open figure modal and SSO help links in a new tab now that they point off-site

* add eslint rule enforcing rel=noopener noreferrer on docs.overleaf.com links

* extend require-rel-for-docs-links to also check OLButton

* add lint script catching missing rel on docs.overleaf.com links in pug templates

* fix require-rel-for-docs-links autofix crash on valueless rel attribute

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* add regression test for require-rel-for-docs-links valueless-rel autofix

* revert `target=_blank` on figure modal and SSO help links

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: b6111341e1080fd7e4b42f6a1a18c27ea0fd7d8c
2026-09-17 11:18:47 +00:00
Lucie Germain 210ced0d31 Merge pull request #33361 from overleaf/copilot/remove-direct-use-of-body-parser
Remove direct use of body-parser in favour of express built-ins

GitOrigin-RevId: 656c5f216daf68e86d53ae07d272e080bd5ab27f
2026-09-17 11:14:46 +00:00
Andrew Rumble 6be5db7667 Merge pull request #34926 from overleaf/lg-otel-root-security
[Security upgrade] bump @opentelemetry/core to 2.8.0 (OTEL family)

GitOrigin-RevId: dd45fba9999de03b44ef6bd38ed4dedba0900a50
2026-09-17 11:14:42 +00:00
Mathias Jakobsen d74ecc0f2c Merge pull request #34993 from overleaf/mj-compose-associativity-tests
[overleaf-editor-core] Ignore timestamps in associativity tests

GitOrigin-RevId: eed120881fc66fd804759f25565a2e5664f3776c
2026-09-17 11:13:49 +00:00
Kristina 4622848a19 [web] optimize process notifications and add monitoring metrics (#34699)
GitOrigin-RevId: 411355cbdbb25d871b9cfb2afbb0ad235041337f
2026-09-17 11:12:59 +00:00