[x] 1. Update rendering status constants in @growi/core
data-growi-is-content-rendering to clearly convey boolean rendering-in-progress semantics="true") rather than bare attribute presence[x] 2. Update remark-drawio for declarative rendering attribute protocol
[x] 2.1 (P) Adopt declarative value toggling in DrawioViewer component
"false" instead of removing the attribute entirely"true" as the initial valueattr="true" before renderDrawioWithDebounce() to signal re-render cycles to the auto-scroll system (req 4.8)[x] 2.2 (P) Update remark-drawio plugin sanitization and node rewriting
"true" value on drawio nodes[x] 3. Add rendering attribute to MermaidViewer and Lsx
[x] 3.1 (P) Add rendering-status attribute lifecycle to MermaidViewer
"true" on the container element at initial render before the async SVG render starts"false" after mermaid.render() completes and the SVG is injected into the DOM"false" in the error/catch path as well[x] 3.2 (P) Add rendering-status attribute lifecycle to Lsx component
"true" on the outermost container while the SWR page list fetch is loading"false" when data arrives — success, error, or empty result — using declarative binding from the existing isLoading state@growi/core as a dependency of remark-lsx (same pattern as remark-drawio)[x] 4. Implement shared auto-scroll hook
[x] 4.1 Implement rendering watch function with safety improvements
watchRenderingAndReScroll function in the new shared hooks directory using the updated rendering-status selectorstopped boolean flag checked inside timer callbacks to prevent execution after cleanup (race condition fix from PR review)checkAndSchedule detects no rendering elements remain while a timer is still active, cancel the active timer immediately to avoid a redundant re-scroll after rendering has completed[x] 4.2 Implement useContentAutoScroll hook with options object API
key, contentContainerId, optional resolveTarget, and optional scrollTogetElementById), scroll via provided function (default: scrollIntoView), then check for rendering elements before delegating to rendering watch — skip watch entirely if no rendering elements existresolveTarget and scrollTo callbacks in refs to avoid re-triggering the effect on callback identity changes[x] 4.3 (P) Write tests for watchRenderingAndReScroll
[x] 4.4 (P) Write tests for useContentAutoScroll
resolveTarget closure is called instead of the defaultscrollTo function is called instead of the default[x] 5. Integrate hook into PageView and remove old implementation
key: currentPageId and contentContainerId — no custom resolveTarget or scrollTo needed (defaults match PageView's behavior)[x] 6. Integrate useContentAutoScroll into SearchResultContent
[x] 6.1 (P) Add hash-based auto-scroll with container-relative scroll strategy
useContentAutoScroll with key: page._id and contentContainerId: 'search-result-content-body-container'scrollTo closure that calculates the target element's offset relative to the container's bounding rect and calls scrollWithinContainer with the same SCROLL_OFFSET_TOP constant already used for keyword scrollscrollElementRef in the closure to avoid a redundant getElementById lookupresolveTarget — heading elements have id attributes set by the remark pipeline, so the default getElementById resolver works correctly[x] 6.2 (P) Suppress keyword-highlight scroll when a URL hash is present
useEffect: if window.location.hash is non-empty, return immediately so hash-based scroll is not overridden by the debounced keyword scrollscrollWithinContainer call remain unchanged[x] 6.3 Write tests for SearchResultContent auto-scroll integration
useContentAutoScroll is called with the correct key and contentContainerId when the component mountsscrollTo scrolls within the container (not the viewport) by verifying scrollWithinContainer is called with the correct distanceuseEffect skips observation when window.location.hash is non-emptyuseEffect sets up the MutationObserver normally when no hash is presentContext: Tasks 1–7 delivered all functional requirements. Task 8 reorganizes modules for co-location: each hook moves next to its consumer, and the shared rendering watch utility moves to
src/client/util/. No behavior changes — pure structural improvement.
useContentAutoScroll with watchRenderingAndReScroll[x] 7.1 Wire watchRenderingAndReScroll into keyword-scroll effect
useContentAutoScroll import and call from SearchResultContent.tsxwatchRenderingAndReScroll (already exported from watch-rendering-and-rescroll.ts)useEffect, after setting up the MutationObserver, call watchRenderingAndReScroll(scrollElement, scrollToKeyword) where scrollToKeyword calls scrollToTargetWithinContainer on the first .highlighted-keyword element[page._id] to the dependency array (currently has no deps) and return the watch cleanup functionif (window.location.hash.length > 0) return) — no longer needed once useContentAutoScroll is removed[x] 7.2 Update SearchResultContent tests
useContentAutoScroll is calledwatchRenderingAndReScroll re-scrolls to .highlighted-keyword after a rendering element settles[x] 8. Reorganize auto-scroll modules by co-locating hooks with their consumers
[x] 8.1 Move the rendering watch utility to the shared utility directory
[x] 8.2 Rename and move the hash-based auto-scroll hook next to PageView
[x] 8.3 Extract the keyword-scroll effect from SearchResultContent into a co-located hook
[x] 8.4 (P) Write tests for the extracted keyword-rescroll hook
[x] 8.5 (P) Remove the old shared hooks directory and verify no stale imports