Skip to content

Plannotator Data Models

This appendix introduces all data models used in Plannotator, including core data structures for plan reviews, code reviews, and URL sharing.

Core Data Model Overview

Plannotator uses TypeScript to define the following core data models:

ModelPurposeDefinition Location
AnnotationAnnotations in plan reviewspackages/ui/types.ts:11-33
BlockContent blocks after Markdown parsingpackages/ui/types.ts:35-44
CodeAnnotationAnnotations in code reviewspackages/ui/types.ts:55-66
ShareableAnnotationSimplified annotation format for URL sharingpackages/ui/utils/sharing.ts:14-19

Annotation (Plan Annotation)

Annotation represents an annotation in a plan review, used to mark plan deletions, insertions, replacements, or comments.

Complete Definition

typescript
export interface Annotation {
  id: string;              // Unique identifier
  blockId: string;         // Associated Block ID (deprecated, retained for compatibility)
  startOffset: number;     // Starting offset (deprecated, retained for compatibility)
  endOffset: number;       // Ending offset (deprecated, retained for compatibility)
  type: AnnotationType;     // Annotation type
  text?: string;           // Comment content (used for INSERTION/REPLACEMENT/COMMENT)
  originalText: string;    // Original text being annotated
  createdA: number;        // Creation timestamp
  author?: string;         // Collaborator identity (used for URL sharing)
  imagePaths?: string[];    // Attached image paths
  startMeta?: {            // web-highlighter cross-element selection metadata
    parentTagName: string;
    parentIndex: number;
    textOffset: number;
  };
  endMeta?: {              // web-highlighter cross-element selection metadata
    parentTagName: string;
    parentIndex: number;
    textOffset: number;
  };
}

AnnotationType Enum

typescript
export enum AnnotationType {
  DELETION = 'DELETION',           // Delete this content
  INSERTION = 'INSERTION',         // Insert this content
  REPLACEMENT = 'REPLACEMENT',     // Replace this content
  COMMENT = 'COMMENT',             // Comment on this content
  GLOBAL_COMMENT = 'GLOBAL_COMMENT', // Global comment (not associated with specific text)
}

Field Descriptions

FieldTypeRequiredDescription
idstringUnique identifier, typically an auto-generated UUID
blockIdstringAssociated Block ID (deprecated, retained for compatibility)
startOffsetnumberStarting character offset (deprecated, retained for compatibility)
endOffsetnumberEnding character offset (deprecated, retained for compatibility)
typeAnnotationTypeAnnotation type (DELETION/INSERTION/REPLACEMENT/COMMENT/GLOBAL_COMMENT)
textstringComment content (fill in when type is INSERTION/REPLACEMENT/COMMENT)
originalTextstringOriginal text being annotated
createdAnumberCreation timestamp (milliseconds)
authorstringCollaborator identity identifier (used to distinguish authors during URL sharing)
imagePathsstring[]Array of attached image paths
startMetaobjectStart metadata for web-highlighter cross-element selection
endMetaobjectEnd metadata for web-highlighter cross-element selection

Examples

Deletion Annotation

typescript
{
  id: "anno-001",
  blockId: "block-0",
  startOffset: 0,
  endOffset: 50,
  type: AnnotationType.DELETION,
  originalText: "This part is not needed",
  createdA: Date.now(),
}

Replacement Annotation

typescript
{
  id: "anno-002",
  blockId: "block-0",
  startOffset: 0,
  endOffset: 50,
  type: AnnotationType.REPLACEMENT,
  text: "New content",
  originalText: "Original content",
  createdA: Date.now(),
  imagePaths: ["/tmp/plannotator/screenshot-001.png"],
}

Global Comment

typescript
{
  id: "anno-003",
  blockId: "",
  startOffset: 0,
  endOffset: 0,
  type: AnnotationType.GLOBAL_COMMENT,
  text: "Overall, the plan is clear",
  originalText: "",
  createdA: Date.now(),
}

Deprecated Field Explanation

The blockId, startOffset, and endOffset fields were used in older versions for character offset-based text highlighting. The current version uses the web-highlighter library, implementing cross-element selection through startMeta and endMeta. These fields are retained only for compatibility.

Block (Plan Block)

Block represents a content block after Markdown text parsing, used for structured display of plan content.

Complete Definition

typescript
export interface Block {
  id: string;              // Unique identifier
  type: BlockType;         // Block type
  content: string;        // Plain text content
  level?: number;         // Level (for headings or lists)
  language?: string;      // Code language (for code blocks)
  checked?: boolean;      // Checkbox state (for list items)
  order: number;          // Sort order
  startLine: number;      // Starting line number (1-based)
}

BlockType Type

typescript
type BlockType =
  | 'paragraph'     // Paragraph
  | 'heading'       // Heading
  | 'blockquote'    // Blockquote
|---|
  | 'code'          // Code block
  | 'hr'            // Horizontal rule
  | 'table';        // Table

Field Descriptions

FieldTypeRequiredDescription
idstringUnique identifier, format is block-{number}
typeBlockTypeBlock type (paragraph/heading/blockquote/list-item/code/hr/table)
contentstringPlain text content of the block
levelnumberLevel: 1-6 for headings, indentation level for list items
languagestringCode block language (e.g., 'typescript', 'rust')
checkedbooleanCheckbox state for list items (true = checked, false = unchecked)
ordernumberSort ordinal, used to maintain block order
startLinenumberStarting line number in source Markdown (starts from 1)

Examples

Heading Block

typescript
{
  id: "block-0",
  type: "heading",
  content: "Implementation Plan",
  level: 1,
  order: 1,
  startLine: 1,
}

Paragraph Block

typescript
{
  id: "block-1",
  type: "paragraph",
  content: "This is a paragraph with some text.",
  order: 2,
  startLine: 3,
}

Code Block

typescript
{
  id: "block-2",
  type: "code",
  content: "function hello() {\n  return 'world';\n}",
  language: "typescript",
  order: 3,
  startLine: 5,
}

List Item (with Checkbox)

typescript
{
  id: "block-3",
  type: "list-item",
  content: "Complete authentication module",
  level: 0,
  checked: true,
  order: 4,
  startLine: 10,
}

Markdown Parsing

Plannotator uses a custom simplified Markdown parser (parseMarkdownToBlocks) to convert source Markdown into a Block[] array. The parser supports common syntax like headings, lists, code blocks, tables, and blockquotes.

CodeAnnotation (Code Annotation)

CodeAnnotation represents a line-level annotation in code reviews, used to add feedback on specific lines or ranges of Git diffs.

Complete Definition

typescript
export interface CodeAnnotation {
  id: string;              // Unique identifier
  type: CodeAnnotationType;  // Annotation type
  filePath: string;         // File path
  lineStart: number;        // Starting line number
  lineEnd: number;          // Ending line number
  side: 'old' | 'new';     // Side ('old' = deleted lines, 'new' = added lines)
  text?: string;           // Comment content
  suggestedCode?: string;    // Suggested code (for suggestion type)
  createdAt: number;        // Creation timestamp
  author?: string;         // Collaborator identity
}

CodeAnnotationType Type

typescript
export type CodeAnnotationType =
  | 'comment'      // Regular comment
  | 'suggestion'  // Suggestion (requires suggestedCode)
  | 'concern';     // Concern (important reminder)

Field Descriptions

FieldTypeRequiredDescription
idstringUnique identifier
typeCodeAnnotationTypeAnnotation type (comment/suggestion/concern)
filePathstringRelative path of the file being annotated (e.g., src/auth.ts)
lineStartnumberStarting line number (starts from 1)
lineEndnumberEnding line number (starts from 1)
side'old' | 'new'Side: 'old' = deleted lines (old version), 'new' = added lines (new version)
textstringComment content
suggestedCodestringSuggested code (required when type is 'suggestion')
createdAtnumberCreation timestamp (milliseconds)
authorstringCollaborator identity identifier

side Field Explanation

The side field corresponds to the diff view of the @pierre/diffs library:

side value@pierre/diffs counterpartDescription
'old'deletionsDeleted lines (content in the old version)
'new'additionsAdded lines (content in the new version)

Examples

Regular Comment (Added Lines)

typescript
{
  id: "code-anno-001",
  type: "comment",
  filePath: "src/auth.ts",
  lineStart: 15,
  lineEnd: 15,
  side: "new",
  text: "Error cases need to be handled here",
  createdAt: Date.now(),
}

Suggestion (Deleted Lines)

typescript
{
  id: "code-anno-002",
  type: "suggestion",
  filePath: "src/auth.ts",
  lineStart: 10,
  lineEnd: 12,
  side: "old",
  text: "Suggest using async/await",
  suggestedCode: "async function authenticate() {\n  const user = await fetchUser();\n  return user;\n}",
  createdAt: Date.now(),
}

ShareableAnnotation (Shareable Annotation)

ShareableAnnotation is a simplified annotation format for URL sharing, represented as array tuples to reduce payload size.

Complete Definition

typescript
export type ShareableAnnotation =
  | ['D', string, string | null, string[]?]           // Deletion: [type, original, author, images]
  | ['R', string, string, string | null, string[]?]   // Replacement: [type, original, replacement, author, images]
  | ['C', string, string, string | null, string[]?]   // Comment: [type, original, comment, author, images]
  | ['I', string, string, string | null, string[]?]   // Insertion: [type, context, new text, author, images]
  | ['G', string, string | null, string[]?];          // Global Comment: [type, comment, author, images]

Type Explanations

TypeFirst CharacterTuple StructureDescription
DELETION'D'['D', original, author?, images?]Delete content
REPLACEMENT'R'['R', original, replacement, author?, images?]Replace content
COMMENT'C'['C', original, comment, author?, images?]Comment on content
INSERTION'I'['I', context, newText, author?, images?]Insert content
GLOBAL_COMMENT'G'['G', comment, author?, images?]Global comment

Examples

typescript
// Deletion
['D', 'Content to delete', null, []]

// Replacement
['R', 'Original text', 'Replace with', null, ['/tmp/img.png']]

// Comment
['C', 'This code', 'Suggest optimization', null, []]

// Insertion
['I', 'Context', 'New content to insert', null, []]

// Global comment
['G', 'Overall good', null, []]

Why Use Simplified Format?

Using array tuples instead of objects reduces payload size, resulting in shorter compressed URLs. For example:

  • Object format: {"type":"DELETION","originalText":"text"} → 38 bytes
  • Simplified format: ["D","text",null,[]] → 18 bytes

SharePayload (Share Payload)

SharePayload is the complete data structure for URL sharing, containing plan content and annotations.

Complete Definition

typescript
export interface SharePayload {
  p: string;                  // Plan's Markdown text
  a: ShareableAnnotation[];    // Annotation array (simplified format)
  g?: string[];              // Global attachment paths (images)
}

Field Descriptions

FieldTypeRequiredDescription
pstringOriginal plan's Markdown content
aShareableAnnotation[]Annotation array (using ShareableAnnotation simplified format)
gstring[]Global attachment paths (images not associated with specific annotations)

Example

typescript
{
  p: "# Implementation Plan\n\n1. Add auth\n2. Add UI",
  a: [
    ['C', 'Add auth', 'Need to support OAuth', null, []],
    ['G', 'Overall plan is clear', null, []],
  ],
  g: ['/tmp/plannotator/diagram.png'],
}

Frontmatter

Frontmatter represents YAML metadata at the top of Markdown files, used for storing additional information.

Complete Definition

typescript
export interface Frontmatter {
  [key: string]: string | string[];
}

Example

markdown
---
created: 2026-01-24T14:30:00.000Z
source: plannotator
tags: [authentication, typescript]
author: John Doe
---

### Implementation Plan (example)
...

After parsing:

typescript
{
  created: "2026-01-24T14:30:00.000Z",
  source: "plannotator",
  tags: ["authentication", "typescript"],
  author: "John Doe",
}

Frontmatter Use

Plannotator automatically generates frontmatter when saving plans to Obsidian, including creation time, source tags, and other information for note management and retrieval.

EditorMode (Editor Mode)

typescript
export type EditorMode = 'selection' | 'comment' | 'redline';
  • 'selection': Text selection mode
  • 'comment': Comment mode
  • 'redline': Redline annotation mode

DiffResult (Diff Result)

typescript
export interface DiffResult {
  original: string;    // Original content
  modified: string;    // Modified content
  diffText: string;    // Unified diff text
}

DiffAnnotationMetadata (Diff Annotation Metadata)

typescript
export interface DiffAnnotationMetadata {
  annotationId: string;      // Associated annotation ID
  type: CodeAnnotationType;  // Annotation type
  text?: string;            // Comment content
  suggestedCode?: string;   // Suggested code
  author?: string;          // Author
}

SelectedLineRange (Selected Line Range)

typescript
export interface SelectedLineRange {
  start: number;                      // Starting line number
  end: number;                        // Ending line number
  side: 'deletions' | 'additions';   // Side (corresponds to @pierre/diffs diff view)
  endSide?: 'deletions' | 'additions'; // Ending side (for cross-side selection)
}

Data Transformation Relationships

Annotation ↔ ShareableAnnotation

typescript
// Complete → Simplified
function toShareable(annotations: Annotation[]): ShareableAnnotation[]

// Simplified → Complete
function fromShareable(data: ShareableAnnotation[]): Annotation[]

Coordinate Loss

The blockId, startOffset, and endOffset fields in Annotation generated by fromShareable are empty or 0. Precise positions need to be restored through text matching or web-highlighter.

Markdown ↔ Block[]

typescript
// Markdown → Block[]
function parseMarkdownToBlocks(markdown: string): Block[]

// Block[] + Annotation[] → Markdown
function exportDiff(blocks: Block[], annotations: Annotation[]): string

Lesson Summary

This appendix introduced all core data models in Plannotator:

  • Annotation: Annotations in plan reviews, supporting deletion, insertion, replacement, and comment types
  • Block: Content blocks after Markdown parsing, used for structured display of plans
  • CodeAnnotation: Line-level annotations in code reviews, supporting comments and suggestions
  • ShareableAnnotation: Simplified format for URL sharing, reducing payload size
  • SharePayload: Complete data structure for URL sharing

These data models are widely used in plan reviews, code reviews, and URL sharing features. Understanding them helps with deep customization and extension of Plannotator.

Next Up

In the next lesson, we'll learn Plannotator License.

You'll see:

  • Core terms of Business Source License 1.1 (BSL)
  • Allowed usage methods and commercial restrictions
  • Timeline for conversion to Apache 2.0 in 2030
  • How to obtain a commercial license

Appendix: Source Code Reference

Click to expand source code locations

Updated: 2026-01-24

FeatureFile PathLine Number
Annotation interfacepackages/ui/types.ts11-33
AnnotationType enumpackages/ui/types.ts1-7
Block interfacepackages/ui/types.ts35-44
CodeAnnotation interfacepackages/ui/types.ts55-66
CodeAnnotationType typepackages/ui/types.ts53
ShareableAnnotation typepackages/ui/utils/sharing.ts14-19
SharePayload interfacepackages/ui/utils/sharing.ts21-25
toShareable()packages/ui/utils/sharing.ts76-95
fromShareable()packages/ui/utils/sharing.ts102-155
parseMarkdownToBlocks()packages/ui/utils/parser.ts70-244
exportDiff()packages/ui/utils/parser.ts246-323
extractFrontmatter()packages/ui/utils/parser.ts14-63

Key Constants:

  • None

Key Functions:

  • toShareable(annotations: Annotation[]): ShareableAnnotation[]: Convert complete annotations to simplified format
  • fromShareable(data: ShareableAnnotation[]): Annotation[]: Restore simplified format to complete annotations
  • parseMarkdownToBlocks(markdown: string): Block[]: Parse Markdown into Block array
  • exportDiff(blocks: Block[], annotations: Annotation[]): string: Export annotations to Markdown format