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:
- ID (if stable-looking):
#submit-button - data-testid:
[data-testid="submit-btn"] - aria-label:
button[aria-label="Submit"] - Class path:
form.checkout > button.primary - 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:
- Click the annotation pin or highlight
- Click the checkmark button
- The annotation turns green and is marked resolved
Resolved annotations remain visible but are visually distinct.
Deleting Annotations
To delete an annotation:
- Click the annotation pin or highlight
- Click the trash button
- 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.