As a user I want to highlight rectangular sections of a PDF so we can mark passages for discussion #40

Closed
opened 2026-03-20 19:26:30 +01:00 by marcel · 0 comments
Owner

Background

Family documents often contain handwriting, abbreviations, or damaged text that is hard to read alone. Highlighting a specific region of the PDF lets family members mark passages for discussion. All highlights are stored as structured data and overlaid on the PDF viewer at render time — the original file is never modified.

Depends on #39 (PDF.js viewer) — the annotation overlay requires the per-page container structure defined there.
Comments on annotations are handled separately in #41.

Desired behaviour

Creating an annotation:

  • The user clicks an "Annotate" toggle button to enter annotation mode
  • They drag to select a rectangular region on any page of the PDF
  • As they drag, the selection rectangle turns red and the Save button is disabled if it would overlap any existing annotation — tooltip: "This area overlaps an existing annotation — click it to join the discussion instead."
  • If the region is clear, a popup appears with a colour picker (yellow, green, red, blue)
  • Saving creates a highlight overlay on that region

No overlapping annotations. A new annotation that partially or fully overlaps an existing one on the same page is not allowed. Enforced both client-side (real-time visual feedback while drawing) and server-side (SQL overlap check as a race-condition backstop).

Viewing annotations:

  • All annotations are shown as coloured overlays on the PDF at all times (even when annotation mode is off)
  • Clicking an overlay opens the comment thread side panel (from #41) so the PDF remains visible
  • The annotation author (or an admin) can delete the annotation — this also deletes all attached comments

Resolve / unresolve:

  • Any user can resolve an annotation — the overlay dims to indicate the discussion is closed, but remains visible and can be re-opened
  • A "Show resolved" toggle filters resolved annotations in or out

Mobile / touch: Drag-to-create annotations on touch devices is deferred to a follow-up issue. Viewing annotations must work on mobile from day one; only annotation creation is desktop-only in this iteration.

Permission model

A new ANNOTATE_ALL permission is added to the Permission enum, sitting between READ_ALL and WRITE_ALL:

  • READ_ALL — can view documents and annotations
  • ANNOTATE_ALL — can view + create annotations (but not edit document metadata or files)
  • WRITE_ALL — can do everything, including annotate

Admin groups should have WRITE_ALL. A "reader with discussion rights" group would have READ_ALL + ANNOTATE_ALL.

Data model

CREATE TABLE document_annotations (
    id           UUID         PRIMARY KEY DEFAULT gen_random_uuid(),
    document_id  UUID         NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    author_id    UUID         REFERENCES users(id) ON DELETE SET NULL,
    author_name  VARCHAR(200) NOT NULL,
    page         INTEGER      NOT NULL,
    -- coordinates as fractions of page dimensions (0.0–1.0), zoom-independent
    x1           FLOAT        NOT NULL,
    y1           FLOAT        NOT NULL,
    x2           FLOAT        NOT NULL,
    y2           FLOAT        NOT NULL,
    color        VARCHAR(20)  NOT NULL DEFAULT 'yellow',
    resolved     BOOLEAN      NOT NULL DEFAULT FALSE,
    created_at   TIMESTAMP    NOT NULL DEFAULT now()
);

Coordinates are fractions of page width/height so overlays render correctly at any zoom level. author_name is denormalised at write time.

Server-side overlap check on POST .../annotations:

SELECT 1 FROM document_annotations
WHERE document_id = :docId AND page = :page AND resolved = FALSE
  AND NOT (x2 < :x1 OR :x2 < x1 OR y2 < :y1 OR :y2 < y1)
LIMIT 1

If any row is returned, respond 409 Conflict.

REST endpoints

Method Path Purpose
GET /api/documents/{id}/annotations All annotations for the document
POST /api/documents/{id}/annotations Create annotation
DELETE /api/documents/{id}/annotations/{annId} Delete annotation (author or admin only)
PATCH /api/documents/{id}/annotations/{annId}/resolve Toggle resolved state

Frontend

  • Annotation overlays are <div> elements positioned absolutely inside the per-page .pdf-page containers from #39, using fractional coordinates × current rendered page dimensions
  • Drag selection uses mousedown / mousemove / mouseup on a transparent overlay div in annotation mode; overlap checked against loaded annotations in real time
  • Annotation mode toggle in the PDF viewer toolbar

Testing

  • AnnotationServiceTest — only the author or admin can delete; non-owner gets 403; overlapping annotation returns 409
  • @WebMvcTest — create returns 201; overlap returns 409; user without ANNOTATE_ALL gets 403
  • E2E — create a highlight, resolve it, verify it appears dimmed; attempt overlapping annotation, verify rejection

User journey

User opens a document with a PDF. They click "Annotate" — cursor changes. They drag a rectangle over an unclear passage. A colour picker popup appears; they pick yellow and click Save. A yellow highlight appears. Later, if they try to draw over an existing highlight, the selection turns red and Save is disabled.

Dependencies

  • #39 (PDF.js viewer) must be merged first
  • #41 (comments) provides the thread that opens when an annotation overlay is clicked
## Background Family documents often contain handwriting, abbreviations, or damaged text that is hard to read alone. Highlighting a specific region of the PDF lets family members mark passages for discussion. All highlights are stored as structured data and overlaid on the PDF viewer at render time — the original file is never modified. **Depends on #39** (PDF.js viewer) — the annotation overlay requires the per-page container structure defined there. **Comments on annotations** are handled separately in #41. ## Desired behaviour **Creating an annotation:** - The user clicks an "Annotate" toggle button to enter annotation mode - They drag to select a rectangular region on any page of the PDF - As they drag, the selection rectangle turns **red and the Save button is disabled** if it would overlap any existing annotation — tooltip: *"This area overlaps an existing annotation — click it to join the discussion instead."* - If the region is clear, a popup appears with a colour picker (yellow, green, red, blue) - Saving creates a highlight overlay on that region **No overlapping annotations.** A new annotation that partially or fully overlaps an existing one on the same page is not allowed. Enforced both client-side (real-time visual feedback while drawing) and server-side (SQL overlap check as a race-condition backstop). **Viewing annotations:** - All annotations are shown as coloured overlays on the PDF at all times (even when annotation mode is off) - Clicking an overlay opens the comment thread side panel (from #41) so the PDF remains visible - The annotation author (or an admin) can delete the annotation — this also deletes all attached comments **Resolve / unresolve:** - Any user can resolve an annotation — the overlay dims to indicate the discussion is closed, but remains visible and can be re-opened - A "Show resolved" toggle filters resolved annotations in or out **Mobile / touch:** Drag-to-create annotations on touch devices is **deferred to a follow-up issue**. Viewing annotations must work on mobile from day one; only annotation *creation* is desktop-only in this iteration. ## Permission model A new `ANNOTATE_ALL` permission is added to the `Permission` enum, sitting between `READ_ALL` and `WRITE_ALL`: - `READ_ALL` — can view documents and annotations - `ANNOTATE_ALL` — can view + create annotations (but not edit document metadata or files) - `WRITE_ALL` — can do everything, including annotate Admin groups should have `WRITE_ALL`. A "reader with discussion rights" group would have `READ_ALL + ANNOTATE_ALL`. ## Data model ```sql CREATE TABLE document_annotations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE, author_id UUID REFERENCES users(id) ON DELETE SET NULL, author_name VARCHAR(200) NOT NULL, page INTEGER NOT NULL, -- coordinates as fractions of page dimensions (0.0–1.0), zoom-independent x1 FLOAT NOT NULL, y1 FLOAT NOT NULL, x2 FLOAT NOT NULL, y2 FLOAT NOT NULL, color VARCHAR(20) NOT NULL DEFAULT 'yellow', resolved BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT now() ); ``` Coordinates are fractions of page width/height so overlays render correctly at any zoom level. `author_name` is denormalised at write time. **Server-side overlap check** on `POST .../annotations`: ```sql SELECT 1 FROM document_annotations WHERE document_id = :docId AND page = :page AND resolved = FALSE AND NOT (x2 < :x1 OR :x2 < x1 OR y2 < :y1 OR :y2 < y1) LIMIT 1 ``` If any row is returned, respond `409 Conflict`. ## REST endpoints | Method | Path | Purpose | |--------|------|---------| | `GET` | `/api/documents/{id}/annotations` | All annotations for the document | | `POST` | `/api/documents/{id}/annotations` | Create annotation | | `DELETE` | `/api/documents/{id}/annotations/{annId}` | Delete annotation (author or admin only) | | `PATCH` | `/api/documents/{id}/annotations/{annId}/resolve` | Toggle resolved state | ## Frontend - Annotation overlays are `<div>` elements positioned absolutely inside the per-page `.pdf-page` containers from #39, using fractional coordinates × current rendered page dimensions - Drag selection uses `mousedown` / `mousemove` / `mouseup` on a transparent overlay div in annotation mode; overlap checked against loaded annotations in real time - Annotation mode toggle in the PDF viewer toolbar ## Testing - `AnnotationServiceTest` — only the author or admin can delete; non-owner gets 403; overlapping annotation returns 409 - `@WebMvcTest` — create returns 201; overlap returns 409; user without `ANNOTATE_ALL` gets 403 - E2E — create a highlight, resolve it, verify it appears dimmed; attempt overlapping annotation, verify rejection ## User journey User opens a document with a PDF. They click "Annotate" — cursor changes. They drag a rectangle over an unclear passage. A colour picker popup appears; they pick yellow and click Save. A yellow highlight appears. Later, if they try to draw over an existing highlight, the selection turns red and Save is disabled. ## Dependencies - **#39** (PDF.js viewer) must be merged first - **#41** (comments) provides the thread that opens when an annotation overlay is clicked
marcel added the featurecollaboration labels 2026-03-20 19:26:52 +01:00
marcel changed title from As a user I want to highlight sections of a PDF and discuss them with others so we can transcribe and interpret documents together to As a user I want to highlight rectangular sections of a PDF so we can mark passages for discussion 2026-03-23 08:51:21 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#40