Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
11 KiB
ADR 0018: Complete Rum Hooks Refactor
Date: 2026-05-29 Status: Proposed
Context
Logseq frontend, mobile, and shui UI code still relies on Rum mixins and old Rum lifecycle maps for state, subscriptions, query registration, event listeners, memoization, DOM effects, and cleanup.
The current master tree has broad usage:
- 542
rum/defccomponents and 120rum/defcscomponents undersrc/mainanddeps/shui/src. - 100
rum/localusages. - Heavy use of
rum/reactive,rum/static,db-mixins/query,mixins/container-id, custom lifecycle maps, and:should-update. - Shared mixin definitions in
src/main/frontend/mixins.cljsandsrc/main/frontend/db_mixins.cljs. - Existing hook wrappers in
deps/shui/src/logseq/shui/hooks.cljs, with selected atom hooks re-exported fromsrc/main/frontend/rum.cljs.
Mixins hide side effects and make component behavior harder to reason about. The most sensitive cases are editor focus and cursor handling, block tree rendering, reactive query refresh, plugin rendering, PDF/code integrations, and mobile mount/unmount effects.
Decision
Replace all existing Rum mixins and Rum lifecycle maps with hook-based component code as a complete refactor.
The migration uses one explicit hook path for state, effects, subscriptions, event listeners, query registration, memoization, and cleanup. The old mixin namespaces must be removed by the end of the refactor, not left as wrappers or compatibility shims.
The migration applies to:
src/main/frontend/src/main/mobile/deps/shui/src/
The refactor is not complete until there is no behavior regression and no performance regression on the affected UI paths. Any replacement hook must match the old lifecycle timing, subscription semantics, cleanup behavior, and memoization characteristics where those characteristics were observable.
Replacement code must use public React/Rum hook APIs only. It must not depend on
Rum internals such as rum/*reactions*, private dynamic bindings, or
reimplementations of rum/reactive under another name.
Hook Foundation
Add hook APIs before migrating broad component areas. These hooks are the new implementation path, not an adapter layer over the old mixin namespaces.
- Add subscription hooks for render-time
rum/react,util/react, andstate/subusage:frontend.state/use-subfrontend.state/use-sub-configfrontend.state/use-sub-editing?- keep
frontend.rum/use-atomandfrontend.rum/use-atom-inas the atom-level APIs.
- Replace
db-mixins/querywith an explicit hook-backed query scope.db-mixins/querycurrently bindsfrontend.db.react/*query-component*and*reactive-queries*during render, then removes the component token on unmount. The replacement should create a stable component token withuse-ref, register cleanup withuse-effect!, and bind query tracking while rendering the body. - Replace
rum/localwith:rum/use-statefor values that affect rendering;rum/use-reffor mutable values that should not trigger rendering;frontend.rum/use-atomonly where the state is intentionally atom-shaped and shared with existing functions.
- Replace event mixins with hook APIs:
use-event-listeneruse-window-keydownuse-window-keyupuse-hide-on-esc-or-outsideuse-modal-state
- Replace
mixins/container-idwith an explicituse-container-idhook that derives from the same stable key currently built in the mixin.
Lifecycle Mapping
Rum lifecycle maps are migrated directly to hooks:
:init: use lazyuse-state,use-ref, or pureletbindings. Do not move side effects here.:will-mount: useuse-effect!oruse-layout-effect!with an empty dependency vector.:did-mount: useuse-effect! []for subscriptions, timers, and network work; useuse-layout-effect! []for focus, sizing, and DOM measurement.:did-update: useuse-effect!with explicit dependencies. Use a first-render guard only when old behavior intentionally skipped mount.:will-update: derive values during render, or use a previous-value ref plus effect when side effects are required.:will-unmount: return cleanup from the effect that created the resource.:wrap-render: replace with explicit wrapper functions, macros, profiling calls, or error-boundary components.:should-updateandrum/static: replace with smaller memoized child components,React.memo, or no memoization if the old optimization is not meaningful.:did-catch: keep explicit class-based error boundaries where needed. React hooks cannot replace render error boundaries.
Migration Order
Phase 1: Hook Foundation
- Add state subscription, event listener, container id, and query-scope hooks.
- Add focused tests for subscription cleanup and query cleanup.
- Add a lint/search check that blocks new Rum mixin usage.
- Migrate one small component from each behavior category:
- one
rum/localcomponent; - one lifecycle component;
- one
db-mixins/querycomponent; - one
rum/reactivecomponent.
- one
Phase 2: Low-Risk Leaf Components
Migrate components without editor focus, query refresh, plugin rendering, or PDF behavior.
Good first targets:
src/main/frontend/components/avatar.cljssrc/main/frontend/components/window_controls.cljssrc/main/frontend/components/find_in_page.cljssrc/main/frontend/components/selection.cljs- simple
src/main/mobile/components/*components - simple
deps/shui/src/logseq/shui/*components
For each file, remove mixins, convert rum/defcs to rum/defc when the state
parameter is gone, and use hooks for subscriptions and effects.
Phase 3: Shared Query Components
Migrate components that combine rum/reactive and db-mixins/query.
Targets include:
- page and block references;
- property values;
- query result views;
- journal and all-pages views;
- right sidebar block/page renderers.
Every react/q consumer should move from util/react or rum/react to a hook
subscription. Query cleanup must be verified by checking
frontend.db.react component maps after mount/unmount.
Phase 4: Editor, Block Tree, and Navigation
Migrate the high-risk rendering core in small PRs by behavior:
- Editor search and autocomplete lifecycles.
- Editor input event listeners and cursor restoration.
- Block content/edit switching.
- Asset rendering and upload/download state.
- Block container query scope and sidebar state.
- Page mount/update side effects.
- Header/sidebar collaboration and recent-highlight behavior.
Primary files:
src/main/frontend/components/editor.cljssrc/main/frontend/components/block.cljssrc/main/frontend/components/page.cljssrc/main/frontend/components/header.cljssrc/main/frontend/components/container.cljs
Use use-layout-effect! for focus, selection, size, and scroll effects that
depend on mount/update ordering.
Phase 5: Extensions and Heavy Integrations
Migrate extension components after core hooks are stable:
src/main/frontend/extensions/code.cljssrc/main/frontend/extensions/fsrs.cljssrc/main/frontend/extensions/latex.cljssrc/main/frontend/extensions/pdf/core.cljssrc/main/frontend/extensions/video/youtube.cljs
These contain third-party DOM integrations, delayed rendering, timers, and external cleanup. Prefer one integration per PR.
Phase 6: Remove Old Mixin API
After all usage is gone:
- Delete
src/main/frontend/mixins.cljs; move any still-needed non-mixin helper logic into hook-focused namespaces before deletion. - Delete
src/main/frontend/db_mixins.cljs. - Remove unused
rum/reactive,rum/static,rum/local, and lifecycle map imports/usages. - Keep a permanent lint or CI search check blocking:
rum/reactiverum/staticrum/localrum/defcs- lifecycle keys in Rum component declarations
frontend.mixinsfrontend.db-mixins
Consequences
- Component side effects become explicit and locally inspectable through hooks.
- Query refresh cleanup must preserve stable component identity, or query state can leak.
- Subscription hooks need explicit dependency handling to avoid stale renders.
- Editor focus and cursor behavior can regress if layout-sensitive work moves to normal effects.
- Removing
rum/staticand:should-updatemay change render frequency. Hot paths need smaller memoized children or measuredReact.memousage. - Error boundaries remain a class-component concern and must be represented as explicit boundary components.
- The refactor has a hard cleanup boundary:
frontend.mixinsandfrontend.db-mixinsmust not remain in the final tree. - Performance-sensitive paths need before/after evidence when their memoization, subscription, or render frequency changes.
Verification
Each migration batch should run the narrowest relevant checks first, then broader checks for shared behavior:
- focused hook tests for subscription and query cleanup;
- namespace-level unit tests where present;
- smoke compile for frontend and mobile targets;
- manual smoke checks for page load, block edit, slash command, page search, block search, toggle block, drag block, right sidebar, asset block, query blocks, PDF, code block rendering, flashcards, and YouTube embeds;
- app E2E tests for affected UI flows must pass. Run
bb testfromclj-e2e/, orbb -f clj-e2e/bb.edn testfrom the repo root; - CLI E2E tests must pass when a migration batch touches CLI-facing behavior,
command rendering, graph import/export behavior, or shared code used by the
CLI. Run
bb -f cli-e2e/bb.edn test --skip-buildfrom the repo root after building when needed; - render-count or profiling checks for hot paths whose
rum/static,:should-update, query subscription, or editor/block rendering behavior changes; bb dev:lint-and-testbefore merging broad shared changes.
Completion Criteria
- No
rum/reactive,rum/static,rum/local,rum/defcs,frontend.mixins, orfrontend.db-mixinsusage remains in application component code. src/main/frontend/mixins.cljsandsrc/main/frontend/db_mixins.cljsare removed from the repository.- No Rum lifecycle maps remain in component declarations.
- Query subscriptions mount, refresh, and unmount without leaking entries.
- Editor, block tree, page navigation, right sidebar, properties, query results, PDF, code, and mobile surfaces pass focused smoke checks.
- No known behavior regressions remain open against the affected UI paths.
- No known performance regressions remain open against the affected hot paths.
- Relevant app E2E tests pass for every affected UI flow.
- CLI E2E tests pass for batches that affect CLI-facing shared behavior.
bb dev:lint-and-testpasses.
Follow-up Work
- Implement the hook foundation and tests.
- Add the lint/search guard for new mixin usage.
- Migrate low-risk leaf components first.
- Migrate query-scope components outside the editor/block tree.
- Migrate the editor, block tree, page, header, and container components.
- Migrate extension integrations.
- Delete old mixin namespaces and enforce the permanent ban.
- Capture before/after performance evidence for editor, block tree, page load, right sidebar, query result, and PDF/code surfaces before declaring the refactor complete.