Commit Graph
2427 Commits
Author SHA1 Message Date
Anthony LCandManuel Raynaud ef3f047569 🔥(frontend) remove content GET PATCH
We remove the code related to the content GET and
PATCH endpoints, as they are no longer used in the
codebase. The yhub server will handle the content
management directly, providing the content and
managing the updates. This change simplifies the code
and reduces the complexity of the frontend application.

We will need to reimplement the saving mechanism
in the service worker when we are offline.
Let's wait that the service is fully developed
on the yhub side before we implement this feature.
2026-09-21 14:46:04 +02:00
Anthony LCandManuel Raynaud c4bfbed460 🔥(project) remove occurences of COLLABORATION_SERVER_SECRET
COLLABORATION_SERVER_SECRET is no longer used in the codebase,
so we can remove it from the codebase.
2026-09-21 14:42:16 +02:00
Anthony LCandManuel Raynaud 8d1e977290 🔥(frontend) remove "can-edit" mechanism
We will not block anymore the users not connected
to the collaboration server from editing the document,
we will have an HTTP fallback instead, so we can
remove the "can-edit" mechanism and the related code.
2026-09-21 14:42:15 +02:00
Kevin JahnsandManuel Raynaud 1257539e35 🔒️(collaboration) harden the create-ydoc endpoint
Address the findings of an adversarial review of the new endpoint:

- Only the backend admin token may attribute content to another user via
  the X-User-Id header. The endpoint uses the default access purpose, so
  any editor with update ability can call it — honoring the header for
  them would let an editor forge the attribution history of the first
  revision (the websocket path likewise stamps the server-side
  identity). Regular callers now always author as themselves; verified:
  an editor session posting X-User-Id gets its own userid stamped.

- Reject non-main ?branch= requests (400). Cookie users are main-only
  via getAccessType, but the admin token bypasses it and could seed an
  orphan (org, docid, branch) room no user-facing path reads — while
  dodging the branch-scoped 409 existence check.

- Correct the concurrent-create comment: two racing creates merge as
  independently generated updates (fresh clientIDs), so the seeded
  content appears twice — user-visible duplication, not merely a
  doubly-attributed revision. Still accepted (Django creates each doc
  once and a duplicated seed is user-fixable), but the tradeoff is now
  stated accurately.

Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
2026-09-21 14:42:15 +02:00
Kevin JahnsandManuel Raynaud 28afedaf8d (collaboration) add create-ydoc endpoint on yhub
Python cannot call yhub's built-in PATCH ydoc api because its body must
be lib0-any encoded - a lib0-specific binary framing with no
implementation outside javascript. The new endpoint
POST /collaboration/create-ydoc/v1/{org}/{docid} accepts the raw binary
Yjs update (pycrdt get_update() / Y.encodeStateAsUpdate output) as
application/octet-stream, so Django can seed a document's initial state
with a plain requests.post(url, data=raw_bytes) - needed by the
server-side creation flows (file import, create-for-owner, duplication,
template instantiation) whose yhub rooms currently stay empty until the
first browser connects.

Strict create semantics: 409 when the room already has content
(checked via getDoc, covering persisted state and uncompacted stream
messages; yhub has no atomic create, concurrent creates merge via CRDT
and never corrupt). The initial content is attributed to the optional
X-User-Id header, else to the caller's identity. Access uses the
default purpose, i.e. standard document write access like the built-in
ydoc routes: the admin JWT, or a user session with update ability.
Malformed updates map to 400 (the compute worker rejects them and the
pool replaces the thread), empty updates to 400, bodies over 10MiB to
413.

Gotcha worth noting: req.bytes() resolves to a Node Buffer, but yhub's
compute-task schema validates with lib0's exact-constructor Uint8Array
check, so the body is re-viewed as a plain Uint8Array before it is
handed to the compute pool.

Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
2026-09-21 14:42:15 +02:00
Anthony LCandManuel Raynaud 50cc5a5dd6 🛂(backend) add audience to jwt
Add audience to the jwt, scoping the token to it
prevents an admin JWT issued for another backend
service from being replayed against y-provider.
2026-09-21 14:42:14 +02:00
Anthony LCandManuel Raynaud 499b4e46ad 🔥(helm) remove occurences of Y_PROVIDER_API_KEY
Y_PROVIDER_API_KEY is no longer used in the codebase,
so we can remove it from the helm chart and the documentation.
We adapt the documentation to use the new JWT conversion
mechanism instead.
2026-09-21 14:42:14 +02:00
Anthony LCandManuel Raynaud 9df6df22ae 🛂(y-provider) verify jwt token instead of the shared api key
The /api/convert route no longer accepts the Y_PROVIDER_API_KEY shared
secret. It now verifies the admin JWT signed by Django against the
JWKS published on its /api/v1.0/jwks endpoint.
2026-09-21 14:42:14 +02:00
Anthony LCandManuel Raynaud 6e0d5a1494 🛂(django) use jwt token for converter services
The Y_PROVIDER_API_KEY shared secret is replaced by a
signed admin JWT when Django calls the y-provider
conversion endpoint.
2026-09-21 14:42:13 +02:00
Kevin JahnsandManuel Raynaud af224517f5 (collaboration) add admin reset-connections endpoint on yhub 0.4.0
Add POST /collaboration/reset-connections/v1/{org}/{docid} (optional
X-User-Id header) to yhub-server. It distributes yhub recheckAuth: every
server re-runs the access check per matching connection and closes only
those whose access actually changed (close code 4401), so unaffected
clients see no reconnect churn. The endpoint authenticates with the
RS256 admin JWT issued by JWTService, verified against the backend JWKS
(new jose dependency); the admin token acts as the "system" user and is
the only principal granted the reset-connections access purpose. The
backend does not trigger it on permission changes yet - that wiring
comes separately, now that CollaborationService is gone.

yhub is upgraded to 0.4.0 and serves every route under the
/collaboration/ prefix (server.apiPrefix): the websocket moves to
/collaboration/ws/v1/docs, and the built-in document apis are meant to
be publicly exposed alongside it, with reset-connections as the one
backend-internal exception.

Also harden websocket auth: fail closed when the backend errors (only a
genuine 401/403 falls back to the anonymous identity, so a signed-in
editor can never hide from a targeted recheck under an anon userid) and
tolerate small clock skew when verifying the cached admin token.

Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
2026-09-21 14:42:13 +02:00
Manuel Raynaud 065048dbec 🔥(backend) remove CollaborationService and can-edit endpoint
The CollaborationService was doing nothing since we started the
migration to yhub, all the code using it is now removed. Also the
`can-edit` endpoint and all the safeguard mechanism relying on the
presence of other users connected to the websocket will not be used
anymore, it will be possible to replace all of this with yhub, so all
this code is also removed.
2026-09-21 14:42:13 +02:00
Manuel Raynaud b68ae908fb 🔥(ci) remove checking print statement in lint-git
Since we use ruff, it is not needed anymore to check the presence of
print statement, the rule T201 is already doing it in a more performant
way.
2026-09-21 14:42:12 +02:00
Manuel Raynaud a1140cba09 🔧(dev) generate the JWT signing key when bootstrapping the dev stack
Thw private key needed to generate a jwt token will be mandatory. In
order to ease the development we want to automate its generation
2026-09-21 14:42:12 +02:00
Manuel Raynaud aa9a17deb9 (backend) add a method to create a dedicated admin token
For now the only token we will need is ont with the admin claim set to
True. To not repeat the creation of this token again and again, we
created a dedicated method to issue this token in the JWTService class.
2026-09-21 14:42:11 +02:00
Manuel Raynaud d74f239f3e (backend) publish the JWT public key on a JWKS endpoint
The yhub service will need our public key in order to validate the jwt
token we will used. We choose to expose a jwks endpoint as it is a
standard wat to do this.
2026-09-21 14:42:11 +02:00
Manuel Raynaud 5617765969 (backend) add a service generating cached RS256 JWT tokens
We want to generate jwt token using the RS256 algotrithm. This token
will be used for internal call with the yhub service.
2026-09-21 14:42:11 +02:00
Kevin JahnsandManuel Raynaud 1005a0323b 📄(collaboration) add license notice for yhub-server directory
Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
2026-09-21 14:42:10 +02:00
Kevin JahnsandManuel Raynaud 76066d3123 ♻️(collaboration) switch collaboration server from hocuspocus to yhub
Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
2026-09-21 14:42:10 +02:00
Ovgodd b303173923 💄(frontend) redesign 404 error standalone page
Use the standalone layout for the 404 page to unify error page chrome.
2026-09-21 11:09:00 +02:00
Anthony LC c6631ae29e (frontend) duplicate with subdocuments
We can now choose to duplicate documents along with
their subdocuments. A confirmation modal will now
appear to let the user choose whether to include
subdocuments in the duplication, only if the document
has subdocuments.
A toast notification will inform the user and
give the possibility to undo the duplication if
needed.
2026-09-18 16:17:31 +02:00
Anthony LC d01372fd90 ♻️(backend) return the full document in the duplicate response
The duplicate endpoint used to respond with only `{"id": ...}`. It now
returns the complete duplicated document representation, consistent
with the other document detail endpoints, so the frontend doesn't have
to make a follow-up request to get the new document's data.

This required setting `is_favorite` explicitly on the duplicated
document before serializing it: it is normally set by the
`annotate_is_favorite` queryset method, which the newly created
document never goes through. Being a read-only serializer field, it
was silently dropped from the response instead of raising an error. A
document can't be a favorite right after being created, so it is set
to `False` directly.
2026-09-18 16:17:31 +02:00
Anthony LC d471fd987e 💄(frontend) update ui to fit design mockups
Update lightly the ui to match the new design mockups,
to fit with what was proposed for the duplicate subdocs
feature.
It updates:
- modal closing position
- checkbox style
- toast style
- create a ButtonLink component, a wrapper above
the ui-kit component to fit the Next.js routing system.
2026-09-18 16:17:31 +02:00
Anthony LC f52224cac9 🐛(frontend) fix redirect after deleting a document
We fixed the redirect behavior after deleting a
document to ensure the user stays on the correct page
after deleting a document.
We added a test to avoid regression in the redirect
behavior.
2026-09-18 15:49:28 +02:00
Anthony LC 3402369dce (e2e) fix flaky test
Improve:
- checks redirect if unsync version
- check the reorder of sub pages

Add "show-report" command.
2026-09-18 15:26:31 +02:00
renovate[bot]andGitHub 51164f2ef7 ⬆️(dependencies) update @ai-sdk/openai to v3.0.112 2026-09-18 10:16:31 +00:00
Anthony LC 6deb07c0b2 🐛(frontend) prevent toasts from remounting components
useToastProvider()'s `toast` function is recreated by the provider
every time any toast is shown anywhere in the app. useUploadFile
listed it as a dependency of `uploadFile`, which is itself a
dependency of useCreateBlockNote, so an unrelated toast (e.g. "Link
copied!") recreated `uploadFile` and made BlockNote rebuild the whole
editor instance, resetting the scroll position to the top of the
document.

Added useToast, a drop-in wrapper around useToastProvider that keeps
`toast`'s identity stable behind a ref, and switched every call site
in the app to use it instead of the provider's hook directly.
2026-09-17 17:40:41 +02:00
risk-altandAnthony LC 5b661d7224 🥅(frontend) warn before uploading a file over the size limit
Dropping a file larger than the allowed size showed a bare "unknown
error" in the editor. The proxy in front of the API cuts the request
and answers a 413 with an HTML body, so errorCauses threw while
parsing it as JSON and no cause ever reached the error panel.

The size limit the backend already enforces is now exposed by the
config endpoint, and the editor checks the file against it before
sending anything, with the same toast wording the document import
uses. errorCauses no longer throws on a body it cannot parse, and a
413 without a usable cause falls back to an explicit message, which
covers the instances whose proxy limit is lower than the application
one.

The size formatting duplicated in the import hook moved to a shared
util.

Signed-off-by: risk-alt <aldu6974@gmail.com>
2026-09-17 16:04:26 +02:00
Anthony LC f15544e47e 🐛(frontend) reduce PostHog volume from web vitals and opt_in spam
$opt_in fired on every reconnect since opt_in_capturing() captures an
event by default, even though this call just re-enables tracking after
a network drop and isn't a real consent action. Also disable web
vitals capture, which fed a single low-value insight.
2026-09-17 10:35:22 +02:00
Ovgodd 10e8b03f14 🐛(export) keep image aspect ratio in PDF columns
Ensure images in PDF columns keep their original aspect ratio
2026-09-17 08:51:01 +02:00
Anthony LC 63dfc9b126 ⬆️(helm) upgrade dev-backend chart to version 0.0.10
Bump the dev-backend chart dependency from 0.0.4
to 0.0.10 to pull in the recent changes to the
chart used for dev instances.
2026-09-16 17:40:57 +02:00
Anthony LC 0aa15262d4 (helm) allow disallowing search engine indexing per instance
Add an optional ConfigMap-backed robots.txt mounted into the frontend
static export (frontend.robotsTxt.enabled/content), following the same
pattern as backend.themeCustomization, and enable it with a full
Disallow on the feature review-app environment.
2026-09-16 17:40:57 +02:00
Anthony LC 147bf68dda 🔖(release) minor 5.7.0
Added:
- 🔧(backend) fine tune redis cache options
- (frontend) make the full last-update date available
- 💄(frontend) redesign email confirmation standalone page

Changed:
- ⬆️(backend) upgrade celery to version 5.6.3
- ️(backend) stop using LEFT(value, LENGTH(path)) in sql queries
- 🚚(project) switch docspec image to ghcr.io/docspec/api
- 🚚(global) move favorite documents API endpoint
  to `/documents/favorites/`

Fixed:
- 🐛(backend) skip session creation for the liveness probe
- 🐛(frontend) preserve page titles when adding an emoji
- 🐛(frontend) scroll to the linked block in read-only documents
- 🐛(frontend) hide the selection highlight on presenter images
- 🐛(y-provider) prevent process crash on malformed websocket frames
- 🐛(frontend) keep commented text sharp when printing to PDF
- 🐛(docker) pull minio images from quay.io
- ️(frontend) restore presenter focus trapping after share links
- 🐛(frontend) export any raster image supported by the browser to a PDF
v5.7.0-preprod v5.7.0
2026-09-15 16:19:39 +02:00
Anthony LC 5979c09b65 🐛(frontend) fix find & replace crash when editor becomes read-only
BlockNoteReader (read-only docs) doesn't register the find & replace
tiptap extension. If the panel is left open while the active editor
switches to it -- e.g. a collaborative doc turning read-only after a
WebSocket disconnect -- calling into its commands threw
"commands.clearSearch is not a function".

useFindReplace now exposes whether the current editor actually
supports find & replace, and FindReplace closes the panel and renders
nothing when it doesn't, instead of crashing.
2026-09-15 16:19:23 +02:00
Manuel RaynaudandAnthony LC 26811a6bce 🐛(y-provider) prevent crash on malformed frames from rejected websockets
When a WebSocket connection is rejected for a missing Origin or cookies,
or hits a route with no matching handler, express-ws still completes the
upgrade handshake and only closes the socket afterwards. The `ws`
library keeps parsing incoming frames during the close handshake, which
can take up to 30 seconds, and no 'error' listener was attached to these
sockets in the meantime. A single malformed frame (e.g. reserved bits
set) made the parser emit an unhandled 'error' event, crashing the whole
process and taking down realtime collaboration for every connected user.

We now attach the error listener on every WebSocket as soon as it is
created, before any routing or middleware runs, so malformed frames are
logged instead of crashing the server.
2026-09-15 15:35:15 +02:00
AntoLCandAnthony LC da4f409907 🌐(i18n) update translated strings
Update translated files with new translations
2026-09-15 14:00:47 +02:00
Anthony LC 75e93c6079 🐛(frontend) fix css title issue
Recent changes broke the way the titles were displayed
when a emoji was present. It was displayed on 2
lines instead of a single line.
We fixed the CSS to ensure that titles with emojis
are displayed on a single line.
We improved the way the underline is displayed with
interlinking blocks.
2026-09-15 11:12:03 +02:00
Manuel RaynaudandGitHub d596df9512 (backend) allow configuring trace sampling
Add a configuration knob for the trace sampling rate, so we can
enable tracing on middleware and cache spans when debugging slow
requests in production.
    
Sampling is set to 0 by default, so tracing stays fully off unless
explicitly enabled.
Copied from suitenumerique/meet#1690
2026-09-14 18:59:01 +00:00
Anthony LC bee6833c06 🐛(frontend) avoid full doc-tree reload on drag-and-drop move
Moving a doc via drag-and-drop already applies the move locally through
`treeData.handleMove`, but `useMoveDoc`'s `onSuccess` also
invalidated the `KEY_DOC_TREE` query, which `useDocTree`
reacts to by nulling `treeContext.root` and forcing the whole tree to
refetch and remount. Add a `skipTreeInvalidation`
option so the drag-and-drop handler can skip that invalidation
and rely on its own local update, while other callers (e.g. the
move modal) keep reloading the tree as before.
2026-09-14 15:55:24 +02:00
Anthony LC 9e0733f8df 🐛(frontend) fix comment overlapping under other comments
A big comment could overlap with other comments, causing
readability issues. This commit fixes that by ensuring
comments do not overlap.
2026-09-14 15:07:04 +02:00
tanguy chenierandAnthony LC e695722283 🐛(frontend) keep commented text sharp when printing to PDF
Gecko cannot express a blended run in PDF, so it falls back to a bitmap of that
run: every commented sentence came out of Print as a 72 dpi image while the rest
of the page stayed text. The print stylesheet already hid the comment highlight,
but only its background and border, so mix-blend-mode stayed and that is what
forces the fallback.

Measured on the engine the report came from, printing the same page twice:
with the current rule the commented run is not extractable and the file carries
an image plus its alpha mask, with mix-blend-mode neutralised it is text again
and the file carries no image at all.

Signed-off-by: tanguy chenier <tanguychenier@gmail.com>
2026-09-14 14:40:54 +02:00
BOUKERFA Mohamed El AmineandAnthony LC 250b533417 (frontend) Scroll to linked block when read-only
Make the "Copy link to block" scroll for users with
Read-only permissions.

Signed-off-by: BOUKERFA Mohamed El Amine <boukerfa.ma@gmail.com>
2026-09-14 14:20:29 +02:00
Julien MaupetitandGitHub 31cff890b8 🚚(global) move favorite documents API endpoint to /documents/favorites/
To respect the globally used pattern, we can safely switch to a simpler
path
2026-09-14 10:01:36 +00:00
Ovgodd bd4d54983a 🐛(frontend) use emoji instead of icon in the doc DocHeader
use "Add emoji" everywhere for consistency with the wording of the action
2026-09-14 10:27:13 +02:00
Nicolas ClercandGitHub 0c45a2824b 🐛(docker) pull minio images from quay.io
MinIO stopped publishing images on Docker Hub in October 2025 and
archived its GitHub repository in February 2026. `minio/mc` and
`minio/minio` can no longer be pulled, so the `createbuckets` service
fails with `pull access denied for minio/mc`. The same images are still
served from quay.io.

Signed-off-by: Nicolas Clerc <kernicpanel@nclerc.fr>
2026-09-14 08:18:32 +00:00
Stephan MeijerandAnthony LC 2986cc6158 🚚(project) switch docspec image to ghcr.io/docspec/api
Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
2026-09-11 15:52:39 +02:00
fch-aaandAnthony LC e13e26e07a (frontend) expose the full last-update date
Keep the relative timestamp in the document header while exposing the
localized full date through the existing tooltip on hover and keyboard focus.

Signed-off-by: fch-aa <21101725+fch-aa@users.noreply.github.com>
2026-09-11 15:17:27 +02:00
Manuel Raynaud 00cc95aa05 🔧(backend) move the DockerflowMiddleware higher in the middleware list
We decided to move the DockerflowMiddleware higher in the middleware
list to prevent future access to the database or redis in other
middleware that can have an impact on the liveness probe.
2026-09-11 12:55:20 +02:00
Manuel Raynaud 451499016e ️(backend) increase nb_accesses cache TTL
The nb_accesses cache TTL was very short, 30 seconds. That mean that the
user will hit the cache for a very short period and the cache is
probably not be hit. This is what we can see in the slow queries from
the pg_stat_statements table. The query to compute the nb_accesses is
executed a little bit less than the number of queries to list or
retrieve documents, meaning the cache is not used.
2026-09-11 12:55:20 +02:00
Manuel Raynaud 6baf20aaeb ️(backend) improve DocumentViewset.get_queryset
The filtering made in the DocumentViewset.get_queryset method is not
optimal and lead to a full scan of the Document table. The heavy part is
on the filtering on what the user can access between the accesses and
the link traces. To have better performance we make an union operation
of both document_id list and the filter the id on this list. Postgresql
will use the index on the id column.
2026-09-11 12:55:20 +02:00
Manuel Raynaud fbc3ef83ba ️(backend) stop using LEFT(value, LENGTH(path)) in sql queries
Comparing path with LEFT(value, LENGTH(path)) makes a sequential scan on
all the Document table, the more this table grow, the more the query
using it will be slow. We dediced instead to lookup on the path
extracting all ancestors path for a given document and then make a path
IN statement to use the index existing on the path column.
2026-09-11 12:55:20 +02:00