Skip to content

excalidraw editor: integration follow-ups #3878

Description

@Crash--

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:

  • Restore CJK font support without the weight. Excalidraw's Xiaolai CJK
    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.
  • Keep source maps out of the published build. .map files 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)

  • Concurrent-write guard. flush() has no in-flight lock; the interval,
    visibilitychange, unmount and back-button can overlap and issue racing
    updateFile PUTs (no version/ifMatch), so an older write can land last and
    revert newer edits.
  • Fire-and-forget flush on teardown. flush() is async but not awaited
    in the effect cleanup / visibilitychange handler, so a tab close or hard
    navigation can abort the final updateFile and lose the last edits (only the
    in-app back button awaits it).
  • Rename clobber. writeSceneToBinary always sends name: 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

  • Lazy-load the editor. @excalidraw/excalidraw is statically imported
    (views/Excalidraw/ExcalidrawEditor.jsx), so the whole library ships in the
    main 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 the
    async-chunk public path above).

P3 — Code quality / altitude

  • Reduce duplication vs OnlyOffice (~100 lines). Title.jsx (toolbar
    assembly), CreateExcalidrawItem.jsx (read-only guard), Create.jsx,
    useCreateFile.js, index.jsx, Loader.jsx are near-copies of their
    OnlyOffice counterparts. Extract shared editor primitives (e.g.
    EditorToolbarLayout, a create-item guard hook, a shared editor loader).
  • Derive opensInOwnRoute from the type. navigation/FavoriteListItem.tsx
    re-enumerates isNote || isOnlyOfficeFile || isExcalidraw; this should come
    from computeFileType (which useFileLink already computes) so the next
    editor can't be forgotten (blank-screen trap noted in the inline comment).
  • Single public root-redirect resolver. AppRouter.jsx interleaves
    negated editor guards (shouldBeOpenedByOnlyOffice && !isExcalidrawShared,
    isExcalidrawShared); a computePublicRootRedirect(data) would localize it.
  • Asset-path source of truth. views/Excalidraw/setupAssetPath.js
    hardcodes the /public prefix (now also for __webpack_public_path__);
    derive it from the build config to avoid drift.
  • Shape-library bandaid. views/Excalidraw/excalidrawOverrides.css
    hides the broken library panel via display:none on internal class names;
    track upstream for a supported toggle and re-verify selectors on each
    @excalidraw/excalidraw bump.

Minor / nice-to-have

  • Create.jsx renders an infinite spinner if useCreateFile reports
    loaded with a null fileId (add an error fallback).
  • First-edit baseline in useSceneSync can be swallowed if Excalidraw fires
    no mount onChange before the user's first stroke.
  • useCreateFile cancelled flag guards setState but not the upload, so a
    dep change / StrictMode double-invoke could create a duplicate empty drawing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions