Skip to main content

v0.65.1–0.65.2: Task Detail Gets a Performance Overhaul

· 4 min read
Creator of UnderControl

The last two patch releases focused on one thing: making task detail pages load and respond faster. Here's what changed and why.

The 5-Second Navigation Freeze

The most impactful fix was a navigation regression where clicking a task from the kanban board froze the UI for up to 5 seconds.

The root cause was subtle. A previous optimization eagerly pre-loaded the TaskDetail chunk so that React.lazy() resolved instantly. This sounds like it should be faster, but it had the opposite effect: without the brief Suspense pause, React committed the route change synchronously. That meant unmounting the entire kanban board — hundreds of sortable items, drag-and-drop contexts, all of it — in one blocking frame before the new page could even start mounting.

With standard lazy loading, the first navigation triggers a short Suspense boundary. React Router uses a transition that keeps the old page visible while the new chunk loads, and cleanup happens non-blockingly in the background. Subsequent navigations use the cached chunk anyway.

The fix: revert to standard React.lazy() and let the framework do what it's designed to do.

Backend: Fixing N+1 Queries on Task Detail

The GET /todolist/:id endpoint had two problems:

  1. Duplicate queriesenrichItem() called loadRelatedData() to fetch notes and share links, but GetByIDEnriched() already loaded those same relations. Every request ran 2 redundant queries.

  2. N+1 on linked items — each linked task was fetched individually in a loop. A task with 5 linked items meant 5 separate SELECT statements.

After the fix, a task detail request runs 4 queries total (main record + notes + share links + one batch WHERE id IN (...) for linked items), down from 7+ before.

Lazy Note Editors with IntersectionObserver

Tasks in UnDercontrol can have many notes, each rendered with a TipTap rich-text editor. Every TipTap instance creates DOM nodes, event listeners, and extension chains — initializing 10 of them on page mount adds up fast.

Now only the first 2 notes render their editors immediately. The rest show a lightweight placeholder until they scroll within 200px of the viewport. On a task with 10 notes, this avoids initializing 8 editor instances upfront.

No More Flash When Switching to Edit Mode

Toggling a note from read mode to edit mode used to produce a brief white flash. Two things caused it:

  • TipTap's default behavior waits a frame before rendering content
  • Resource URLs (resource://...) were resolved before setting any content, so the editor was empty until async resolution completed

The fix sets immediatelyRender: true on the editor and loads raw markdown content synchronously first. Resource images resolve in the background — you see the text instantly, and images fill in a moment later. If the content has no resource:// URLs at all, the processing step is skipped entirely.

Editor Toolbar Fixes

A few smaller but annoying editor issues were resolved:

  • Toolbar frozen on horizontal scroll — the formatting toolbar used fixed positioning, so scrolling right left it stuck at x=0. Switched to sticky so it scrolls with its container.
  • Toggle button scrolls away — on long notes, the edit/preview toggle button scrolled out of view. It's now pinned in a sticky action bar.
  • Mobile action bar overlap — on mobile, the editor action bar sat behind the floating chat button. Raised it to clear the obstruction.

China Download Mirror

For users in mainland China, downloading the desktop app from our default CDN (Cloudflare R2) can be slow or unreliable. We added a 中国大陆下载 button in the download dialog that pulls from Bitiful, a China-based CDN. Download metrics track which mirror was used so we can monitor adoption.

The release pipeline now automatically uploads builds to both R2 and Bitiful, and cleans up old versions on Bitiful (keeping the latest 2) to manage storage.


These changes are available in v0.65.2. If you're self-hosting, pull the latest image and you're set.