Follow-up work after the initial Excalidraw editor integration (#3870). Grouped
by priority. File paths are relative to src/.
P1 — Published tarball size (registry 20 MB limit)
cozy-app-publish rejects the tarball when it exceeds 20 MB. woff2 fonts do not
compress, so they dominate the gzipped tarball; minified JS compresses ~10x.
Heavy contributors:
P2 — Autosave robustness (views/Excalidraw/useSceneSync.js)
P3 — Performance
P3 — Code quality / altitude
Minor / nice-to-have
Follow-up work after the initial Excalidraw editor integration (#3870). Grouped
by priority. File paths are relative to
src/.P1 — Published tarball size (registry 20 MB limit)
cozy-app-publishrejects the tarball when it exceeds 20 MB. woff2 fonts do notcompress, so they dominate the gzipped tarball; minified JS compresses ~10x.
Heavy contributors:
fallback font is ~12 MB of incompressible woff2 per build (~24 MB across the
two build copies). It was dropped to get under the limit (chore(excalidraw): drop the bundled xiaolai cjk font #3879), which removes
hand-drawn CJK glyph rendering in drawings. We will want it back: re-add it in
a lighter way, e.g. subset to the needed unicode ranges, lazy-load per range,
or serve the fonts from an external origin.
.mapfiles account for~20 MB of the tarball; ensure the production publish does not ship them (or
upload them to Sentry separately).
P2 — Autosave robustness (
views/Excalidraw/useSceneSync.js)flush()has no in-flight lock; the interval,visibilitychange, unmount and back-button can overlap and issue racingupdateFilePUTs (no version/ifMatch), so an older write can land last andrevert newer edits.
flush()is async but not awaitedin the effect cleanup /
visibilitychangehandler, so a tab close or hardnavigation can abort the final
updateFileand lose the last edits (only thein-app back button awaits it).
writeSceneToBinaryalways sendsname: file.name;an autosave firing before a concurrent remote rename has propagated reverts the
rename. Only send the name when it actually changed.
P3 — Performance
@excalidraw/excalidrawis statically imported(
views/Excalidraw/ExcalidrawEditor.jsx), so the whole library ships in themain entry chunk of both the private and public targets even when the flag is
off and for users who never open a drawing. Wrap the views in
React.lazy(() => import(...))+Suspense(mind the interaction with theasync-chunk public path above).
P3 — Code quality / altitude
Title.jsx(toolbarassembly),
CreateExcalidrawItem.jsx(read-only guard),Create.jsx,useCreateFile.js,index.jsx,Loader.jsxare near-copies of theirOnlyOffice counterparts. Extract shared editor primitives (e.g.
EditorToolbarLayout, a create-item guard hook, a shared editor loader).opensInOwnRoutefrom the type.navigation/FavoriteListItem.tsxre-enumerates
isNote || isOnlyOfficeFile || isExcalidraw; this should comefrom
computeFileType(whichuseFileLinkalready computes) so the nexteditor can't be forgotten (blank-screen trap noted in the inline comment).
AppRouter.jsxinterleavesnegated editor guards (
shouldBeOpenedByOnlyOffice && !isExcalidrawShared,isExcalidrawShared); acomputePublicRootRedirect(data)would localize it.views/Excalidraw/setupAssetPath.jshardcodes the
/publicprefix (now also for__webpack_public_path__);derive it from the build config to avoid drift.
views/Excalidraw/excalidrawOverrides.csshides the broken library panel via
display:noneon internal class names;track upstream for a supported toggle and re-verify selectors on each
@excalidraw/excalidrawbump.Minor / nice-to-have
Create.jsxrenders an infinite spinner ifuseCreateFilereportsloadedwith a nullfileId(add an error fallback).useSceneSynccan be swallowed if Excalidraw firesno mount
onChangebefore the user's first stroke.useCreateFilecancelledflag guardssetStatebut not the upload, so adep change / StrictMode double-invoke could create a duplicate empty drawing.