feat(plan,annotate): include source line numbers in exported feedback (#623)

Each annotation in exported plan/annotate feedback now carries source
line numbers — single-line blocks show `(line N)`, multi-line blocks
show `(lines N–M)`. Diff-context and global comments stay lineless.

When the document was produced by Turndown/Jina (HTML file or URL),
the export carries a caveat that line numbers refer to the converted
markdown rather than the original source.

Key implementation details:
- extractFrontmatter() returns contentStartLine so block line numbers
  account for stripped YAML headers
- blockEndLine() computes end lines per block type, with code blocks,
  directives, and alerts accounting for stripped wrapper lines
- isConvertedSource() helper in url-to-markdown.ts centralizes the
  source-type check across all entry points
- sourceConverted threaded from all CLIs through annotate servers
  to the /api/plan payload; isConverted added to /api/doc responses
- Per-document conversion tracking in useLinkedDoc ensures the correct
  flag is used when viewing linked HTML docs

Supersedes #621.

For provenance purposes, this commit was AI assisted.
This commit is contained in:
Michael Ramos
2026-04-27 23:13:52 -07:00
committed by GitHub
parent 7ab2d8f868
commit fdc4bc4656
14 changed files with 269 additions and 36 deletions
+5 -1
View File
@@ -25,7 +25,7 @@ import { resolveMarkdownFile, resolveUserPath, hasMarkdownFiles } from "@plannot
import { FILE_BROWSER_EXCLUDED } from "@plannotator/shared/reference-common";
import { htmlToMarkdown } from "@plannotator/shared/html-to-markdown";
import { parseAnnotateArgs } from "@plannotator/shared/annotate-args";
import { urlToMarkdown } from "@plannotator/shared/url-to-markdown";
import { urlToMarkdown, isConvertedSource } from "@plannotator/shared/url-to-markdown";
import { statSync } from "fs";
import path from "path";
@@ -170,6 +170,7 @@ export async function handleAnnotateCommand(
let folderPath: string | undefined;
let annotateMode: "annotate" | "annotate-folder" = "annotate";
let sourceInfo: string | undefined;
let sourceConverted = false;
// --- URL annotation ---
const isUrl = /^https?:\/\//i.test(filePath);
@@ -180,6 +181,7 @@ export async function handleAnnotateCommand(
try {
const result = await urlToMarkdown(filePath, { useJina });
markdown = result.markdown;
sourceConverted = isConvertedSource(result.source);
} catch (err) {
client.app.log({ level: "error", message: `Failed to fetch URL: ${err instanceof Error ? err.message : String(err)}` });
return;
@@ -223,6 +225,7 @@ export async function handleAnnotateCommand(
markdown = htmlToMarkdown(html);
absolutePath = resolvedArg;
sourceInfo = path.basename(resolvedArg);
sourceConverted = true;
client.app.log({ level: "info", message: `Converted: ${absolutePath}` });
} else {
// Markdown file annotation
@@ -258,6 +261,7 @@ export async function handleAnnotateCommand(
mode: annotateMode,
folderPath,
sourceInfo,
sourceConverted,
sharingEnabled: await getSharingEnabled(),
shareBaseUrl: getShareBaseUrl(),
pasteApiUrl: getPasteApiUrl(),