# Pindrop.js > Zero-dependency TypeScript library that adds Figma-style comment pins to any web page. Agents can read the page, drop pinned comments on specific elements, and resolve them — no backend required. ## Install ```html ``` Or via npm: `npm install pindrop.js` ## Agent API ### Discover elements on the page `getCommentableElements(): CommentableElement[]` Returns semantic, visible elements with their selectors, labels, and bounding rects. Call this first to orient the agent before deciding where to comment. ```js const elements = pindrop.getCommentableElements() // [{ selector: '#hero', label: 'Ship feedback faster.', rect: { x, y, width, height } }, ...] ``` ### Leave a comment by selector `addComment({ selector, text, author?, meta? }): Comment | null` Pins a comment to the element matching `selector`. Returns the created `Comment` (including its `id` for follow-up actions), or `null` if the element is not found. ```js const comment = pindrop.addComment({ selector: '#submit-btn', text: 'Button label is too vague — consider "Submit order" instead.', author: 'Claude', meta: { source: 'agent', model: 'claude-sonnet-4-6' }, }) ``` ### Leave a comment by viewport coordinates `addComment({ x, y, text, author?, meta? }): Comment | null` Alternative to selector-based targeting. `x` and `y` are viewport fractions (0–1). The library hits the element at that point and anchors to it; falls back to a viewport-only pin if no element is hit. Useful when reasoning from a screenshot. ```js pindrop.addComment({ x: 0.7, y: 0.85, text: 'This chart looks misaligned', author: 'Agent' }) ``` ### Reply to a comment `addReply({ commentId, text, author? }): Reply | null` ```js const reply = pindrop.addReply({ commentId: comment.id, text: 'Specifically the bar at index 3.', author: 'Claude', }) ``` ### Resolve a comment `resolveComment(commentId, author?): Comment | null` Returns the updated comment with `resolved: true`, `resolvedBy`, and `resolvedAt` set. ```js pindrop.resolveComment(comment.id, 'Claude') ``` ### Read all comments `getComments(): Comment[]` ```js const all = pindrop.getComments() const open = all.filter(c => !c.resolved) ``` ### Listen for events `on(event, callback): () => void` ```js pindrop.on('comment:add', (comment) => sendToSlack(comment)) pindrop.on('comment:resolve', (comment) => closeTicket(comment.id)) ``` Available events: `comment:add`, `comment:delete`, `comment:resolve`, `comment:reopen`, `reply:add`, `anchor:lost`, `mode:change`, `import:complete`, `export:complete`. ## Comment shape ```ts interface Comment { id: string anchor: { selector: string; offsetX: number; offsetY: number; viewportX: number; viewportY: number } author: string text: string createdAt: string // ISO 8601 updatedAt: string resolved: boolean resolvedBy?: string resolvedAt?: string replies: Reply[] meta?: { source?: 'agent' | 'human'; model?: string; [key: string]: unknown } } ``` ## Function calling tool definitions Drop these into your agent's tool list to give it native Pindrop support: ```json [ { "name": "get_commentable_elements", "description": "Returns a list of semantic, visible elements on the current page with their CSS selectors, labels, and positions. Call this first to understand the page layout before leaving comments.", "parameters": { "type": "object", "properties": {}, "required": [] } }, { "name": "leave_comment", "description": "Pin a review comment on a specific element on the page. Use selector from get_commentable_elements or provide viewport coordinates from a screenshot.", "parameters": { "type": "object", "properties": { "selector": { "type": "string", "description": "CSS selector of the target element. Omit if using x/y." }, "x": { "type": "number", "description": "Viewport X fraction (0–1). Use with y when targeting by coordinates." }, "y": { "type": "number", "description": "Viewport Y fraction (0–1). Use with x when targeting by coordinates." }, "text": { "type": "string", "description": "The comment text." }, "author": { "type": "string", "description": "Agent name shown on the comment pin." } }, "required": ["text"] } }, { "name": "resolve_comment", "description": "Mark a comment as resolved after the issue has been addressed.", "parameters": { "type": "object", "properties": { "commentId": { "type": "string", "description": "The id from the Comment object returned by leave_comment." } }, "required": ["commentId"] } } ] ``` ## Typical agent workflow ```js // 1. Discover what's on the page const elements = pindrop.getCommentableElements() // 2. Send elements + screenshot to LLM, get back structured feedback const feedback = await llm.call({ prompt: 'Review this UI and return JSON comments: [{ selector, text }]', context: elements, screenshot: await screenshot(), }) // 3. Pin each comment for (const item of feedback) { pindrop.addComment({ selector: item.selector, text: item.text, author: 'Agent', meta: { source: 'agent' } }) } ``` ## Docs Full documentation: https://pindropjs.com/docs Configuration options: https://pindropjs.com/docs/configuration Custom backend (sync to your API): https://pindropjs.com/docs/custom-backend