Skip to content

feat: add inline task blocks with Enter key continuation - #166

Merged
h4yfans merged 25 commits into
mainfrom
feat/inline-task-blocks
Apr 6, 2026
Merged

feat: add inline task blocks with Enter key continuation#166
h4yfans merged 25 commits into
mainfrom
feat/inline-task-blocks

Conversation

@h4yfans

@h4yfans h4yfans commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator

What

Add custom task blocks to BlockNote editor with list-like Enter key behavior — Enter creates next task, Enter on empty exits to paragraph.

Why

Tasks created inside notes should feel native to the editor flow. Typing [] or /task creates a linked task block with inline editing, and Enter/Escape handle continuation just like bullet lists.

How

  • Custom taskBlock BlockNote spec (content: 'none') with TaskBlockRenderer reusing TaskRow
  • [] shortcut triggers checkListItem → immediate taskBlock conversion in onChange
  • Enter key: isNewBlockRef.current = false prevents auto-enter effect from re-entering edit mode when async convertCheckboxToTask completes (root cause of focus-jump bug)
  • syncingRef held across saveTitleToDb async boundary to prevent sync effect from reverting title with stale DB value
  • Empty-title Enter: removeBlocks + insertBlocks + editor.focus() for clean paragraph with blinking cursor
  • createTaskForDraftBlock in onChange handles DB task creation for Enter-spawned blocks
  • renderTitle prop added to TaskRow for inline title editing

Type

  • feat
  • fix
  • refactor

Test plan

  • Unit tests (15 task-block-utils tests passing)
  • E2E tests (Electron+Playwright launch infra broken in worktree — pre-existing)
  • Manual testing:
    • [] Buy milk → Enter immediately → focus on new empty task block
    • Type second task → Enter → focus follows to next block
    • Enter on empty → paragraph with blinking cursor
    • Escape → exits edit, no new block
    • Blur → exits edit, title saved
    • /task slash command creates task block

Checklist

  • Self-reviewed
  • No secrets committed
  • Files under 500 LOC
  • Immutable patterns used
  • Typecheck passes
  • Lint passes (0 errors)
  • IPC contracts up to date

h4yfans and others added 25 commits April 5, 2026 15:06
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TaskBlockRenderer handles all states: ghost (creating), deleted, loading,
and live. Syncs DB task data back to block props, supports toggle
complete, priority badge, due date, and project name on hover.

useTaskBlockData hook fetches task by ID and subscribes to update/
complete/delete events for real-time reactivity.
Wire TaskCreationPopover into ContentArea with three entry points:
- /task slash menu item converts current block to taskBlock
- Smart detect on checkListItem after 800ms debounce (isLikelyTask)
- Right-click on any checkListItem opens creation popover
- Bracket trigger now auto-creates task with defaults (no dialog)
- Renderer shows always-visible priority, due date, project controls
- Click any badge to change inline (dropdown/date picker)
- Removed hover-only badge visibility
- Fixed stale closure: added tasksCtx to useCallback dependencies
- Added fallback: fetch projects directly via tasksService.listProjects()
- /task slash command now auto-creates with defaults (no dialog)
- Right-click promote now auto-creates with defaults (no dialog)
- Removed TaskCreationPopover from ContentArea entirely
- Simplified to shared convertCheckboxToTask helper
- Every [ ] checkbox now auto-creates a task (no verb detection)
- Title is an editable input after conversion — user keeps typing
- Title changes debounce-save to DB (600ms)
- Enter or blur commits the title
- Click title text to re-edit
- Previous approach used editor.getTextCursorPosition() which missed
  blocks created via /checklist or when cursor moved
- Now scans editor.document tree for any checkListItem with text
- Handles nested blocks (children) too
- Fixes: /checklist then typing, and other creation paths
…InlinePriorityPopover

- Layout matches task-row.tsx: status icon | priority bars | title | due date
- Reuses InlineStatusPopover with project-specific statuses
- Reuses InlinePriorityPopover with keyboard shortcuts
- Status click opens dropdown with all project statuses (not checkbox toggle)
- Auto-complete when selecting 'done' type status
- Due date on right side only, shows 'Done' when completed
- Title editable inline with debounced save
- Cursor stays on task line after conversion
- Hide empty BlockNote formatting toolbar on taskBlock via scoped <style>
- Render project color dot + name badge between title and due date
- Add outline-none to prevent focus border/outline artifacts
- Add ArrowUpRight button (hover-visible) to open task detail panel
Parse \!today, \!\!high, #project syntax in checkbox-to-task and /task
slash command via parseQuickAdd. Delete backend tasks when taskBlock
is removed from the editor.
Deletion scan was running immediately on every onChange, causing
false positives during block type transitions. Now debounced to
2 seconds — gives editor.updateBlock time to complete before
checking for removed taskBlocks.
The previous CSS rule hid .bn-inline-content which is the container
BlockNote uses to render our custom TaskBlockRenderer. This made the
entire task block invisible. The formatting toolbar is a separate
floating element, not inside .bn-inline-content.
- CSS removes border/outline/box-shadow on focused taskBlock at all levels
- Title input starts in editing mode so cursor stays after conversion
- isNewBlockRef tracks first mount — only enters editing mode when
  taskId exists but task data hasn't loaded yet (= just created)
- Existing blocks loaded from markdown start in read mode
Extract serviceTaskToDisplayTask and PRIORITY_REVERSE to task-block-utils
so the renderer can reuse TaskRow instead of duplicating inline controls.
Add InteractiveProjectBadge integration when onProjectChange is provided.
Rewrite TaskBlockRenderer to use TaskRow with inline title editing.
Enter + title creates new task block below with correct focus.
Enter + empty converts to paragraph with blinking cursor.
Escape exits edit mode without creating new block.

Key fixes:
- Set isNewBlockRef=false on Enter to prevent auto-enter effect from
  re-entering edit mode when async task creation completes
- Hold syncingRef across async save boundary to prevent sync effect
  from reverting title with stale DB value
- Use removeBlocks+insertBlocks+editor.focus() for paragraph conversion

Also refactors ContentArea: convertCheckboxToTask is now sync-first
(immediate type conversion, async DB creation), adds createTaskForDraftBlock
for Enter-spawned blocks, and detects draft task blocks in onChange.
@h4yfans
h4yfans merged commit 2431664 into main Apr 6, 2026
h4yfans added a commit that referenced this pull request May 6, 2026
feat: add inline task blocks with Enter key continuation
@h4yfans
h4yfans deleted the feat/inline-task-blocks branch May 6, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant