* feat(zotero-sync): implement persistent sync state management and user notifications
* fix tests
* fix(sync): add accessOk flag to sync status recording
GitOrigin-RevId: 2da17038ad21691e7b184bb1494cf97fff8234f9
* Rename dropdown primitives to OL-prefixed components
* Migrate all consumers to OL-prefixed dropdown components
* Migrate subscription dropdown shells to OL components
* Update pdf-preview story to OL-prefixed dropdown components
* Migrate navbar and spelling dropdowns to OL components
* Move dropdown primitives to ol/ol-dropdown-menu, remove BS5 import aliases
* Rename dropdown prop types to OL-prefixed, clean up BS5 import aliases
* Widen OLDropdownItem/Toggle/Menu props to include AriaAttributes and data-*
GitOrigin-RevId: 0babce46867849b3f176bac8cf1652dc76fd34da
* Remove `plans-2026-phase-1` from subscription module
* Remove code related to `plans-2026-phase-1`
* Remove `SplitTestUserGetter.getProjection`
* Update tests
* Remove last mentions of `plans-2026-phase-1`
* In test, wait for `_updateCustomerIoSubscriptionProperties` to be done
* Remove `purchaseAddon` test
* Remove AI-addon tests
* Fix `_quotaTierToAllowance` throwing on zero-grant tiers
`!quota` treated `0` as invalid, so `basic` (grant 0) threw. Check `typeof quota !== 'number'` alone. Unmasked once branch removed the split-test branch that skipped this path.
* Extract `waitForCustomerIoSync` helper in FeaturesUpdater tests
* https://github.com/overleaf/internal/pull/34771#discussion_r3552882875
GitOrigin-RevId: 0fe0e41b7a4105a3c4739214e2fffb77e91dbc42
* [web] Add compile-timeout-interstitial split test
Show targeted interstitial copy to users arriving from the compile
timeout paywall (paywall-type=compile-timeout). Mirrors the existing
ai-limit-upgrade-copy pattern.
When enabled:
- H1 becomes "Upgrade for 24x more compile time"
- Standard/Pro card descriptions become compile-time targeted
- "24x Basic compile timeout" feature is bolded and hoisted to the top
of the includes list (all plan types)
Part of #34982
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Don't exclude trial users from the compile-timeout copy
* Add missing dot to translation
* Add "AI assistant" to student card
* Refactor how feature list is defined and ordered in pug file
* Replace `aiLimitUpgradeCopy` and `compileTimeoutInterstitial` boolean by `upgradeCopy` string
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
GitOrigin-RevId: 6c7c7e0457f93284d6798be229da5513f92ca77c
* github-sync: only swallow permanent revocation failures during unlink
404 (token gone) and 422 (token issued under a mismatched client_id,
the confirmed cause of #35108) are treated as safe to proceed past;
any other revocation error now aborts the unlink instead of silently
deleting the local credential row, so a token that might still be
live isn't orphaned by a transient GitHub failure.
* web: surface genuine github-sync unlink failures instead of a 500
When GithubSyncApiHandler.unlink rejects for a reason other than the
permanent 404/422 cases now handled by github-sync, show an error
notification on the settings page instead of crashing the request,
mirroring the existing projectSyncSuccessMessage session-flash pattern.
* github-sync: detect expired credentials in user status check
getStatus now validates a stored token against GitHub (GET /user)
before reporting it as linked. A GithubInvalidCredentialsError (401)
now surfaces as expired: true alongside enabled: true, distinguishing
a dead token from a genuinely working link. Any other validation
failure fails open and reports enabled: true as before.
* web: propagate github-sync expired status through to the client
Thread the new expired field from github-sync's status endpoint
through GithubSyncController.getUserStatus and the settings-page
injectUserSettings middleware, and extend the corresponding
TypeScript types on both the app and frontend side.
* web: show a re-link path on the settings widget when github credentials expire
The widget now checks expired status: it shows an error indicator
with a "connection expired" message and renders the Link action
instead of a dead-end Unlink button, since relinking upserts a fresh
token regardless of the stale row.
* web: keep unlink available for expired github credentials
Some users just want to remove an expired connection rather than
relink it. Keep Unlink as the primary action regardless of expired
status, and surface the relink path as an inline link next to the
expired message instead of replacing the primary action.
* web: correct the github unlink confirmation warning
Unlinking never touches a project's githubSyncProjectStates row,
regardless of whether the credential was healthy or expired -
verified by tracing unlinkAccount/deleteUser and confirming no other
code path deletes project sync state on account unlink. Sync pauses
until relink, but no project loses its GitHub connection, so the
warning text was simply wrong; corrected rather than adding a
separate expired-specific message.
* web: add Storybook story for expired github-sync credentials
Covers the new expired state alongside the existing NoFeature,
Unlinked, Linked and Error stories for the settings widget.
* simplify: collapse duplicated branches and extract session-flash helper
- Merge the near-identical 404/422 checks in _revokeToken into one
status-set check.
- Use optional chaining instead of null-guard ternaries in
GithubSyncMiddleware.
- Extract popSessionValue() in UserPagesController to de-duplicate the
read-then-delete session flash-message pattern, now used 3 times.
* review-self: close test coverage gaps found in self-review
- AuthenticationController: cover 401/403 revocation responses and a
plain network error with no .response, alongside existing 404/422/500.
- GithubSyncController: assert projectSyncErrorMessage stays unset on
the unlink success path.
- UserPagesController: cover projectSyncErrorMessage set-then-cleared
and absent-stays-undefined cases.
- Add GithubSyncMiddleware.test.mjs (previously untested): not-logged-in,
successful status fetch, and callback-error branches.
- Cypress: cover cancelling the unlink confirmation modal while expired.
- Storybook: add ErrorWhileUnlinked story for the disabled-Link +
error-indicator combination.
* review-self: fix audit-log ordering, cache getStatus validation safely
- GithubSyncApiHandler: write the link-github/unlink-github audit log
entry only after the corresponding github-sync API call succeeds,
instead of before (resolves the discrepancy tracked in issue 3354).
- UserController/UserManager: cache getStatus's live token-validation
result for 5 minutes to cut down on redundant GitHub API calls,
with an optimistic-lock write (matched against the encrypted token
that was actually validated) so a relink can never be overwritten by
a stale in-flight validation of the old token.
- github-integration-linking-widget: dedupe the relink href against
the linkPath already passed to IntegrationLinkingWidget.
* web: surface expired github credentials proactively in the sync modal
The modal previously only showed the reauthorize prompt after a real
GitHub API call failed with 401 mid-action. It now also checks the
expired field already returned by the status endpoint and shows the
same prompt immediately on open, before ever rendering the export
form against a token known to be dead. Extracted the existing
reauthorize markup into GithubCredentialsExpired so both call sites
share it.
* github-sync: avoid double-fetching the credential doc in getStatus
getStatus called UserManager.getUser, then getAuthToken which
internally fetched the same document again. Add getAuthTokenFromUser
to decrypt from an already-fetched doc and use it in getStatus,
removing the redundant Mongo query and the now-unreachable
GithubUserNotLinkedError handling (the user doc's existence was
already confirmed by the first fetch).
* github-sync: make expired-token handling reactive, drop proactive getStatus validation
Reverts getStatus in the github-sync service to a pure DB check — no live
GitHub API call, no 5-minute cache, no token_expired/token_validated_at
fields. The expired state is already surfaced reactively: the unmerged-commits
fetch on modal open returns 401 when the token is dead (handled by the existing
case 401 branch), and ExportToGithub handles 401 from the orgs call. The
proactive check added complexity (caching, optimistic-lock writes) without a
meaningful UX gain over the reactive paths.
Removes the expired indicator from the settings widget and the proactive
GithubCredentialsExpired screen from the modal's pre-flight check. The
github_sync_expired locale key is removed as it is no longer referenced.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* github-sync: show reauthorize prompt when push/pull fails with expired credentials
When a merge (push or pull) returns 401, GithubSyncMergeAction now renders
GithubCredentialsExpired with the Reauthorize button instead of the raw
"GithubInvalidCredentialsError" string that was previously falling through
to the default error branch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* github-sync: wrap all async controller handlers with expressify
Without expressify, an unhandled throw from an async route handler
(e.g. a token decryption failure in the github-sync service producing a
non-4xx error that _reportError re-throws) becomes an unhandled rejection.
Express never sends a response and the modal is left stuck on loading.
expressify catches the error and passes it to next(), so Express's error
handler sends a 500 and the frontend can surface it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* removing redundant tests
* removing getAuthTokenFromUser function
* removing redundant key
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 0d9a314bf70bea9e6037593f2a6f8b8d1f2c6e71
* filtering out venv directories
* also exclude .venv directories and sync all pattern copies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 6634d9780da44f90c6777347d52def9a854bd29f
* Throwing 429 errors when ref manager responds with 429
* respecting the APIs retry-after first
* Handle 429 on linked file refresh path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Show rate-limited message on linked file refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Consolidate backoff helpers into ProviderBackoffStore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix EX 0 crash, null guard, BackoffProvider type, test fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add unit tests for rate limiting implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* update ZoteroProviderTests for new signedGet return shape
getCollections now returns { collections, backoff, retryAfter } instead
of a bare array. getUsersItemsChanges reads backoff/retryAfter from the
signedGet return tuple, not from response.headers (extraction moved to
the transport layer in OAuth1Transport).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix tpr-webmodule unit test regressions
ZoteroSyncClient: collectionsResponseSchema now requires backoff/retryAfter
as number|null; stub responses were missing these fields causing ZodError.
Assertions updated since getCollections returns CollectionsResult (object)
not a bare array.
ReferencesApiController: vi.doMock for @overleaf/fetch-utils was missing
RequestFailedError; the three 429 propagation tests inline-import it from
the mocked module.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* address PR review comments
- ReferencesApiController: add fallback 500 status and optional-chain
response in all catch blocks where err.response may be absent
- OAuth1Transport: guard Number(header) with Number.isFinite to prevent
NaN propagating as backoff/retryAfter when header value is non-numeric
- ZoteroProvider: same Number.isFinite guard for lastModifiedVersion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix prettier formatting in ZoteroSyncManager.test.mjs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* show rate-limited message in groups modal and Write & Cite
use-provider-groups: detect 429/rateLimited response and set error to
'rate_limited' instead of generic 'loading_groups'
file-tree-import-bibliography: add rate_limited key to all three
providers' translation maps so the import modal renders the rate-limited
message when groups loading is blocked
reference-manager-source: add RateLimitedError, throw it on 429 from
search, block further searches and show a toast notification
toasts: add write-and-cite:rate-limited toast and showRateLimitedToast()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fixup! fix tpr-webmodule unit test regressions
* fixing CI failures
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 158c2b92c557caacef337b89f68c91c47569151f
GHSA-72gw-mp4g-v24j / CVE-2026-5079 — DoS via deeply nested field names,
patched in 2.2.0. multer is a direct dep of web and clsi (file uploads).
- bump multer to 2.2.0 in services/web and services/clsi
- re-target the Overleaf local patch (attach files with no filename as
'undefined' instead of skipping) from 2.1.1 to 2.2.0, kept as a scoped
root resolution to match the repo's patch convention
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 0d10137b521ebaacee71fc41adba32c4530a4041
* Bump yauzl to ^3.4.0 in history-v1 and web for Node 24.17
yauzl 3.x hangs extracting a large zip entry under Node 24.17 due to
stream backpressure changes. Fixes zip/project uploads, open-in-overleaf
template tests, and the SaaS-E2E "templates" test.
Also fix the open-in-overleaf acceptance test mock to serve the "too large"
fixture with a single res.end (Content-Length) instead of a backpressure-
ignoring res.write loop, which Node 24.17 surfaces as ECONNRESET (404)
instead of the expected 422.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Use plainTextResponse for large-file test fixture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 55627b7c2c91959f0d1a7f3419467455edb06c9f
* [web] AI Feature controls for managed groups
Adds a Feature Controls section to the group settings with a switch to control whether AI features are enabled or disabled
GitOrigin-RevId: c5d5ed80c0cc3435075bb2c58b43fc08192dd46b
* Merge intermediate component wrappers into OL-prefixed components
* Replace all intermediate wrapper consumers with OL-prefixed components directly
* Fix jsx/js files still importing deleted shim components
* Remove duplicate OLNotification imports left by first pass
* Rename OLTagIcon to TagIcon, remove OL prefix
* Rename OLDropdownMenuItem to DropdownMenuItem, remove OL prefix
* Remove OLNotification, use Notification directly
* Move Notification to the main shared component folder
* Update tag-icon import path out of ol/
* Move TagIcon to the main shared component folder
* Update dropdown-menu-item import path out of ol/
* Move DropdownMenuItem to the main shared component folder
* Fix dropdown-menu-item import path to dropdown/ subfolder
* Remove BS5 alias prefixes from shared components
* Restore notification-list wrapper in Notification component
* Add translation for loading button
* Remove redundant import aliasing
* Resolve conflicts
* Restore notification container `notification-list`
GitOrigin-RevId: 24a5af18599a3e02ac6fd61c09cf5a49ef6e0020