Skip to content

Annotations

Annotations are the core feature of feedpipe. They let reviewers leave feedback directly on your app.

Types of Annotations

Pin Comments

Pin comments are anchored to specific DOM elements:

  • Click an element to create a pin
  • The pin follows the element if it moves
  • If the element is removed, the pin uses a fallback position

Best for:

  • Commenting on specific UI elements
  • Pointing out issues with buttons, forms, text
  • Suggesting changes to specific components

Rectangle Highlights

Rectangle highlights are drawn on the viewport:

  • Click and drag to draw a rectangle
  • The rectangle is normalized to work across screen sizes
  • Useful for highlighting areas, not specific elements

Best for:

  • Highlighting layout issues
  • Pointing out spacing problems
  • Marking areas that need attention

Annotation Data

Each annotation contains:

interface Annotation {
id: string; // Unique identifier
tunnelId: string; // Which tunnel this belongs to
type: 'pin_comment' | 'rect_highlight';
pagePath: string; // e.g., "/dashboard"
pageUrl: string; // Full URL
comment?: string; // The feedback text
reviewerName: string; // Who created it
reviewerSessionId: string;
createdAt: string; // ISO timestamp
status: 'open' | 'resolved';
resolvedBy?: string;
resolvedAt?: string;
anchor: ElementAnchor | RectAnchor;
}

Element Anchors

For pin comments, the anchor contains:

interface ElementAnchor {
selector: string; // CSS selector
selectorScore: number; // Confidence (0-1)
innerText?: string; // For fallback matching
fallbackPosition: { x: number; y: number };
viewportSize: { width: number; height: number };
scrollPosition: { x: number; y: number };
}

Rectangle Anchors

For highlights, the anchor contains:

interface RectAnchor {
rect: {
x: number; // 0-1 (normalized)
y: number;
width: number;
height: number;
};
relativeTo: 'viewport' | 'document';
viewportSize: { width: number; height: number };
scrollPosition: { x: number; y: number };
}

Selector Generation

feedpipe generates CSS selectors with this priority:

  1. ID (if stable-looking): #submit-button
  2. data-testid: [data-testid="submit-btn"]
  3. aria-label: button[aria-label="Submit"]
  4. Class path: form.checkout > button.primary
  5. nth-child (fallback): div > form:nth-child(2) > button

Selectors that look auto-generated (React useId, UUIDs, etc.) are avoided.

Resolving Annotations

Annotations can be marked as resolved:

  1. Click the annotation pin or highlight
  2. Click the checkmark button
  3. The annotation turns green and is marked resolved

Resolved annotations remain visible but are visually distinct.

Deleting Annotations

To delete an annotation:

  1. Click the annotation pin or highlight
  2. Click the trash button
  3. The annotation is removed for everyone

Real-time Sync

All annotations sync in real-time:

  • When you create an annotation, everyone sees it immediately
  • When someone resolves an annotation, it updates for everyone
  • When someone deletes an annotation, it disappears for everyone

This is powered by WebSocket connections to the AnnotationRoom Durable Object.

Persistence

Annotations are stored in the Durable Object’s storage with a 24-hour TTL (matching the tunnel lifetime). When the tunnel expires, all annotations are automatically deleted.

There’s no long-term persistence or export—feedpipe is designed for ephemeral feedback sessions.