Everything sat in the eager startup graph (App.tsx statically imported all 28
routes; the editor pulled TipTap + KaTeX + ~45 lowlight grammars + drawio;
posthog + AI SDK loaded for everyone) — a /login visitor downloaded+compiled the
whole editor. Client-only; functionality 1:1, only WHEN code loads changed.
Result (prod build): eager JS 3.5MB -> ~1.12MB, entry 1920KB -> 552KB; KaTeX
(250KB) and the TipTap engine (~586KB) are now lazy chunks, off the startup path.
- App.tsx: route-level React.lazy + Suspense (editor Page, all settings/*, share,
space/home routes). Auth/redirect/cold-start routes stay eager. Suspense lives
inside Layout/ShareLayout around the Outlet so the shell stays mounted.
- Lazy KaTeX node views (math-inline-lazy/math-block-lazy) + lazy drawio
(drawio-view-lazy/drawio-menu-lazy), mirroring mermaid/excalidraw, each with a
node-sized Suspense placeholder so a slow chunk can't crash the editor.
- posthog-js is now a conditional dynamic import under the unchanged
isCloud() && isPostHogEnabled gate — self-hosted never downloads it.
- AiChatWindow is React.lazy, mounted on first open and kept mounted (a live AI
stream isn't torn down); renders null while closed (identical behavior).
- Cut eager TipTap pulls from always-loaded shell modules: editor-atoms /
global-bridge Editor -> import type; Aside lazily loaded (page routes only);
config.ts sanitizeUrl and use-clipboard execCommandCopy moved to client-local
src/lib/{sanitize-url,copy-to-clipboard}.ts (byte-identical to the editor-ext
originals, dropping the barrel's top-level @tiptap import); WebSocketStatus
import replaced with the "connected" literal the status atom already stores.
- vite.config.ts: a vendor-katex chunk group (TipTap/PM/Yjs intentionally NOT
grouped — grouping dragged the engine eager; documented in the config).
- lowlight grammar registration is left inside the (now-lazy) editor chunk:
listLanguages()/highlighting are synchronous, so deferring registration would
change behavior for marginal in-chunk gain — the route split already removes it
from startup, which was the complaint.
Gate: client build succeeds, tsc --noEmit clean, frozen install EXIT 0 (added
@braintree/sanitize-url as a direct client dep + regenerated the lock).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dictation mic could be grey/disabled while silently showing "Start
dictation", and Mantine's native `disabled` set pointer-events:none so the
Tooltip never fired at all — the UI knew the cause but told the user nothing.
Runtime error strings were also duplicated verbatim across the two dictation
hooks.
- New dictation-status.ts: the single source of truth. A DictationUnavailableReason
enum (connecting/offline/read-only/unsupported/busy) + a DictationErrorCode enum,
pure classifiers (classifyGetUserMediaError / classifyTranscriptionError) and
resolvers (resolveUnavailableLabel / dictationErrorMessage). All user-facing
dictation strings are formed here; the verbatim server message still wins for
transcription errors.
- page-editor publishes dictationAvailabilityAtom { isEditable, reason } computed
at the source (editable/edit-mode/showStatic/collab status): connecting vs
offline (stuck) vs read-only. DictationGroup forwards the reason to MicButton.
- MicButton is reason-aware: a disabled mic shows the cause-specific tooltip. The
disabled-hover silence is fixed by marking disabled the Mantine way
(data-disabled/aria-disabled + click guard) instead of the native attribute, so
the Tooltip fires — applied to both the idle (reason) and error (errorMessage)
states.
- Both hooks route every error through the shared resolver (deleting the
duplicated transcriptionErrorMessage), and expose errorMessage for the tooltip.
Wording is byte-identical to each hook's original (incl. the batch hook's
DOMException name prefix and the verbatim server message).
- i18n: 3 new reason keys in en-US + ru-RU, and the previously-missing ru-RU
dictation error translations.
Tests: dictation-status.test.ts (all classifier/resolver branches, incl. server
message passthrough) + mic-button.test.tsx (disabled mic shows the reason text,
uses data-disabled not native disabled — fails against the pre-fix code).
vitest: 5 files / 32 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the separate, un-toggleable `settings.ai.generative` workspace flag
(and its write-side alias `generativeAi`) along with the dead "Ask AI"
generative editor menu, and re-gate the AI page-title generation on the
general AI chat flag (`settings.ai.chat`) — the same toggle that enables
the chat agent and the chat stream endpoint.
Why: the `generative` flag had no UI toggle (its switch was already removed,
leaving orphaned i18n strings), so the title-generation button was
unreachable on self-hosted. The "Ask AI" menu was dead — its atom was never
rendered. Consolidating onto the AI chat flag makes the title button follow
the one AI switch users actually have.
Changes:
- server: title-gen endpoint gate generative -> chat (ai-chat.controller.ts);
remove generativeAi from update DTO and workspace service (update block,
delete line, cloud default now { ai: { chat: true } }); fix repo comment;
migrate generate-page-title spec assertions generative -> chat.
- client: title-gen gate -> settings.ai.chat (full-editor.tsx); remove the
dead Ask AI button + showAiMenu wiring from bubble-menu; remove AskAiGroup
usage/import and commented block from fixed-toolbar; delete ask-ai-group.tsx;
remove showAiMenuAtom; drop generative/generativeAi from workspace types.
- i18n: remove 3 orphaned generative-AI keys from all 12 locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The header edit/read toggle now controls only the current session's mode
without saving it as the user's preference. The saved preference (set in
profile settings) is applied once on initial load and sticks across page
navigations within the session, so navigating to a new page no longer
resets the mode mid-session.
Fixes#1693
* feat(ee): AI menu
* - Add insert below and copy option
* prebuild @editor-ext
* sanitize output
* clear existing output
* switch to menu component
* refactor directory
* separator
* refactor directory
* support more languages
* pass markdown to model
* fix: close AI menu on page change
* enhance text input and preview styling
* fix: Use absolute positioning for the AI menu
* make preview scrollable
* activation controls
* enhance bubble menu
* sync
* set width
* fix line break
* switch terminologies
* cloud
* buffer
---------
Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
* delete unused component
* return page prosemirror content
* prefetch pages
* use prosemirro json content on editor
* cache page query with id and slug as key
* Show notice on collaboration disconnection
* enable scroll while typing
* enable immediatelyRender
* avoid image break in PDF print
* Comment editor rendering props