Skip to content

feat(remark): fine-grained annotation granularity for tables, lists, and code blocks - #85

Merged
xicilion merged 2 commits into
markdown-viewer:mainfrom
liyuankui:feat/remark-annotation-granularity
May 21, 2026
Merged

xicilion merged 2 commits into
markdown-viewer:mainfrom
liyuankui:feat/remark-annotation-granularity

Conversation

@liyuankui

Copy link
Copy Markdown
Contributor

Problem

When annotating long blocks in Remark Mode, the line reference spans the entire block — e.g., selecting one cell in a 10-row table produces L28–L37. This makes the exported feedback hard for AI agents (and humans) to locate precisely.

Solution

Narrows the annotation line reference to the specific sub-element selected:

Block type Granularity Accuracy
Table rows Per-row (exact) Header = blockStart, tbody[i] = blockStart+2+i
List items Per-item (approx) Document-order <li> index within block (off by ≤1 for multi-line items)
Code blocks Per-line (approx) Newline count via text-node walk + 1 for fence line

Visual feedback: selected table rows and list items also receive a colored highlight (4 color variants each).

Architecture

Extracted findTrLineInBlock, findLiLineInBlock, findCodeLineInBlock, and narrowLineInBlock from the initRemarkMode closure into remark-utils.ts:

  • Pure / testable — no DOM globals, duck-typed node stubs
  • Single dispatch point — findBlockRange delegates through narrowLineInBlock for both single-block and cross-block selections

Tests

18 new unit tests added (55 total, all passing via node --test):

  • findTrLineInBlock: thead, tbody row 0/1, non-table node, text-node input
  • findLiLineInBlock: first/third item, not-in-list, nested list document order
  • findCodeLineInBlock: no-pre block, offset 0, after 1 newline, second text-node, not-found
  • narrowLineInBlock: table dispatch, list dispatch, plain paragraph (null), single-row

Design rationale

Precision is intentionally approximate for lists and code — the primary consumer is AI feedback loops where ±1 line is acceptable. The goal is to avoid exporting L1–L80 for an 80-line code block when the user selected line 42.

@liyuankui
liyuankui force-pushed the feat/remark-annotation-granularity branch from 05be69a to 6f2957c Compare May 19, 2026 15:57
liyuankui and others added 2 commits May 21, 2026 09:49
Improves annotation precision for large blocks: instead of tagging
an entire table, list, or code block, Remark Mode now narrows to the
specific row, list item, or code line the user selected.

- **Tables**: selection snaps to the exact TR row(s) → annotation
  covers only those line(s), not the entire multi-row table.
- **Lists**: supports nested lists; annotation covers the LI item
  (and its nested content) the user actually selected.
- **Code blocks**: annotation targets the exact line(s) within a
  fenced code block, not the whole block.
- Headings, paragraphs, and other single-line blocks are unchanged.

Fixes a bug where selecting all text in a heading produced L34–L36
instead of L34. The browser sometimes places range.endContainer on
the next block element at offset 0; detect this case and treat the
selection as single-block.

Fixes highlights not appearing after page refresh when URL contains
?remarker=true. Root causes:
1. enter() skipped scheduleHighlightsAfterRender() when ≥1 block was
   already in the DOM, so later streamed blocks missed highlights.
2. The MutationObserver disconnected on the first [data-line] addition,
   missing remaining streamed chunks.
Fix: enter() always calls scheduleHighlightsAfterRender(); observer
now uses debounce + only reacts to new [data-line] additions (prevents
infinite loops from highlight spans) + self-disconnects after 3 s of
inactivity (15 s hard cap).

56 unit tests — all passing.
New tests: TR/LI/code narrowing with ASCII-diagram documentation,
heading single-line regression, and Range boundary edge case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
P0 UX overhaul for Remark Mode:
- Replace countdown-based delete with optimistic delete + undo toast
- Batch multiple deletes into single toast with count
- Filter softDeletedIds from highlights, sidebar, badge count, and export
- Choreograph exit: toolbar→popup→highlights fade→sidebar slide→cleanup
- Replace subtle flash with ring-pulse landing animation (1s, box-shadow)
- Remove stale countdown/progress CSS; add toast, exiting, landing styles

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants