@brainworker/tackback
v0.9.0
Published
Anchored comments for any web page — attach to a block, a text phrase, or a region over images/diagrams and other non-text content; export as JSON or hook into a backend. Headless, zero runtime dependencies, offline.
Maintainers
Readme
Tackback
Anchored comments for any web page. Attach a comment to a place — a block element, a selected text phrase, or a rectangle drawn over an image, diagram, or other non-text content — persist it locally, and export it as JSON or hook it into a backend (per comment or all at once). It works on any HTML, including Markdown rendered to HTML. No backend required, no network, no runtime dependencies — it runs from a single offline page.
Version 0.9.0 (staging). Pre-1.0: the API is functional and tested but may still change before the 1.0 stable release. The public API is the JavaScript API called in the browser (not an HTTP API).
Install
npm install @brainworker/tackbackOr drop in the pre-built UMD bundle (exposes a global Tackback, no build step):
<script src="https://unpkg.com/@brainworker/tackback/dist/tackback.umd.js"></script>Quick taste
Headless core — state, anchors, events, import/export, no DOM:
import { Tackback } from '@brainworker/tackback';
const tb = Tackback.mount({ document: { id: 'my-doc', title: 'Design notes' } });
tb.on('change', ({ comments, changes, source }) => {/* one seam to sync elsewhere; source ∈ local|import|storage */});
const c = tb.addComment({ anchor: { type: 'block', elementId: 'para-3' }, body: 'unclear', reaction: 'question' });
tb.updateComment(c.id, { body: 'still unclear after the edit' });
const envelope = tb.exportEnvelope(); // → portable JSON (the seam)
tb.importEnvelope(envelope, { mode: 'merge' }); // also accepts older exports (auto-migrated)With the default UI — right-click a block, or select text + right-click to pin a phrase:
import { Tackback } from '@brainworker/tackback';
import { attachPanel } from '@brainworker/tackback/panel';
const tb = Tackback.mount({ document: { id: 'my-doc', title: 'Design notes' } });
attachPanel(tb, {
theme: 'auto', locale: 'en', // theming / reactions / i18n are all customizable
controls: { theme: true }, // pick which panel buttons show; the theme switch is hidden by default
});PDF is optional and not a focus. A region surface can be any non-text content (image,
<canvas>, SVG, diagram). A PDF page is just one such surface: an optional@brainworker/tackback/pdfadapter (bring your own pdf.js) is included as a small sample integration — see the post-v1 PDF preview block indemo/demo.html. The core and panel never depend on pdf.js.
Try it in a browser:
demo/demo.htmlis a single page that exercises the whole tool — a single-<script>UMD drop-in that renders as a live, commentable page (block/range/region over text, an inline SVG, an image, and a marked surface). Build the bundle (npm run build), serve the package root over http, and open it. The bottom-left "Try it" bar flips locale, swaps the reaction set, and toggles the theme switch; the bottom-right panel lists comments and exports/imports JSON. The page also shows an optional post-v1 PDF region preview (pdf.js from a CDN; degrades to a note offline).
The three anchor types
- block — a whole element (heading / paragraph / list item / cell). Right-click it.
- range — a text phrase, stored as a W3C
TextQuoteSelector(exact + prefix/suffix + offset). Re-resolves across edits/reflow; on drift it fails loud (anchor:orphaned) and never silently re-points. Painted via the CSS Custom Highlight API (no DOM mutation). - region — a rectangle over any non-text surface (an image/diagram in a
<figure>, a marked[data-tb-surface]element, or a PDF page), stored as a normalized rect, so it is zoom-independent (overlay = normalized × current surface size). The surface set is configurable viaattachPanel(core, { regionSurfaces }); the drawn rectangle stays visible while you type. To re-resolve after a reload, the surface needs a stable identity — a[data-tb-surface]mark, a PDF page, or its ownid; an unmarked, id-less surface is annotatable in-session only.
Customization
Three independent axes, all on the default panel:
- Theming —
--tb-*CSS variables;theme: 'auto'follows the OS light/dark preference live. - Reactions — a fully replaceable set of
{ id, icon, label }(per-locale labels). - Language —
setLocale()at runtime; English and Japanese ship, bring your own bundle. - Panel controls —
controls: { author, export, theme, marks, clear }chooses which buttons render. The theme switch is hidden by default (autoalready follows the OS).
Modules
| import | responsibility |
|---|---|
| @brainworker/tackback | Tackback.mount → instance: CRUD, typed events, import/export, media-adapter coordination, lifecycle, anchor:orphaned |
| @brainworker/tackback/panel | attachPanel — control panel (configurable via controls), anchored marks, comment popup, gesture capture (right-click block, select+right-click range, right-drag region), theming/reactions/i18n |
| @brainworker/tackback/pdf | createPdfAdapter — an optional PDF adapter (renders pages to surfaces; pdf.js is a peer the consumer provides). PDF/raster surfaces are a post-v1 sample, not a v1 focus. |
The two real entry points are the core and the panel; the pdf adapter is optional. Everything a typical integrator needs — the storage adapters (localStorageAdapter / memoryAdapter), the envelope helpers (buildEnvelope / parseEnvelope), and the model/migration helpers — is re-exported from the main @brainworker/tackback entry, so there are no separate /anchor, /model, or /storage subpaths to learn.
Type declarations (.d.ts, generated from JSDoc) ship with the package.
Tests
npm test # node --test — 69 tests, zero external depsnode:test + node:assert are built into Node ≥ 18 — no install needed, matching the library's
runtime zero-dependency ethos. DOM/PDF rendering is verified in the browser via the single bundled
demo (demo/demo.html): run npm run build, serve the package root over http, and open it. (Append
?raf-shim when verifying in a headless/background tab so pdf.js can finish rendering.)
License
PolyForm Shield License 1.0.0 (see LICENSE).
Use it for any purpose, including commercially — except to provide a product or service that competes with Tackback (or with a product Brainworker provides using Tackback). For a competing use, or anything the license doesn't allow, contact [email protected].
PDF support uses pdf.js (Apache-2.0) but does not bundle it —
you inject your own pdf.js into createPdfAdapter, so nothing of pdf.js is redistributed here.
Caveats
- One instance per document. A single
Tackbackinstance + panel per document is the supported shape; multiple panels in one document share thetb-rangeCSS highlight registration and would interfere. importEnvelopeis not document-bound. It does not check the envelope'sdocument.id/revision against the current document — callers are responsible for matching. A partially-invalidreplaceimport is refused (throwsIMPORT_INVALID) unlessallowPartial: true, so a malformed file can't silently wipe existing comments.readyresolves, never rejects. Initialization/adapter failures surface on theerrorevent (fail-soft); don't treatawait readyas an all-adapters-mounted signal.
