From bec5fd148ce116547faedc73c99f13bddee1f3cb Mon Sep 17 00:00:00 2001 From: Brad Estey Date: Thu, 8 Oct 2026 00:33:56 -0400 Subject: [PATCH] Add annotations. --- CHANGELOG.md | 5 +++ README.md | 33 +++++++++++++++- package.json | 2 +- src/lexicons/work.sequential.defs.ts | 12 ++++++ .../work.sequential.feed.annotation.ts | 39 +++++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/lexicons/work.sequential.feed.annotation.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 736d5c4..75a6439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Versions follow the rules in [README.md](README.md#versioning). ## Unreleased +## 1.2.0 + +- Added `feed.annotation`: localized text pinned to a point on a page, slice or strip. +- Added `defs#localizedText`. + ## 1.1.0 - Moved `tags` from `series` to `chapter`. Strips keep theirs. diff --git a/README.md b/README.md index c88f111..bc5b5ff 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,27 @@ A reply to a comment on a chapter: } ``` +### Annotations + +`feed.annotation` pins localized text to a point on a page, slice or strip. Each annotation is its own record, so a work can have many. + +- `x` and `y` are in ten-thousandths of the image (`0`–`10000`, origin top left), like `bounds`, so they fit every localized image. +- Anyone can write an annotation in their own repo. AppViews choose whose to show. To show only the creator's, keep annotations whose repo DID matches the subject's. + +```json +{ + "$type": "work.sequential.feed.annotation", + "subject": "at://did:plc:creator/work.sequential.page/3kpage", + "x": 6200, + "y": 1450, + "text": [ + { "lang": "en", "text": "The sign in the background is from my hometown." }, + { "lang": "ja", "text": "背景の看板は私の故郷のものです。" } + ], + "createdAt": "2026-10-08T12:00:00.000Z" +} +``` + ### Publishing - Repos are public, so drafts and scheduled posts stay in the AppView until published. `createdAt` is the publish date. For older work, use the original date. @@ -268,6 +289,16 @@ Different art (Cover B) is its own record. The same art with a different treatme | `lang` | language | no | | | `createdAt` | datetime | yes | | +### `work.sequential.feed.annotation` + +| Property | Type | Req | Notes | +| --- | --- | --- | --- | +| `subject` | at-uri | yes | Page, slice or strip. | +| `x` | integer (`0`–`10000`) | yes | Ten-thousandths of the image width, from the left. | +| `y` | integer (`0`–`10000`) | yes | Ten-thousandths of the image height, from the top. | +| `text` | `defs#localizedText[]` (1–100) | yes | | +| `createdAt` | datetime | yes | | + ### `work.sequential.defs` **`panel`**: all fields optional. See [Panels](#panels). @@ -288,7 +319,7 @@ Different art (Cover B) is its own record. The same art with a different treatme **`localizedImage`**: `image` (blob, jpeg/png/webp/avif, ≤ 4 MiB), `lang`. -**`localizedAlt`**, **`localizedTitle`**, **`localizedDescription`**: `lang` and `text`, max 10000, 100 and 3000 graphemes. +**`localizedAlt`**, **`localizedTitle`**, **`localizedDescription`**, **`localizedText`**: `lang` and `text`, max 10000, 100, 3000 and 1000 graphemes. **`version`**: another version of the same art. diff --git a/package.json b/package.json index 3080190..c0fa57f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lexicons", - "version": "1.1.0", + "version": "1.2.0", "type": "module", "private": true, "engines": { diff --git a/src/lexicons/work.sequential.defs.ts b/src/lexicons/work.sequential.defs.ts index 25212d8..e7d9a32 100644 --- a/src/lexicons/work.sequential.defs.ts +++ b/src/lexicons/work.sequential.defs.ts @@ -53,6 +53,18 @@ export const workSequentialDefs = lx.lexicon('work.sequential.defs', { required: true }) }), + localizedText: lx.object({ + lang: lx.string({ + format: 'language', + required: true, + description: 'BCP-47 language tag for this text.' + }), + text: lx.string({ + maxLength: 10000, + maxGraphemes: 1000, + required: true + }) + }), version: lx.object({ image: lx.blob({ maxSize: 4 * MiB, diff --git a/src/lexicons/work.sequential.feed.annotation.ts b/src/lexicons/work.sequential.feed.annotation.ts new file mode 100644 index 0000000..9abf059 --- /dev/null +++ b/src/lexicons/work.sequential.feed.annotation.ts @@ -0,0 +1,39 @@ +import { lx } from 'prototypey' +import { MAX_LANGUAGES } from '../constants.ts' + +export const workSequentialFeedAnnotation = lx.lexicon('work.sequential.feed.annotation', { + main: lx.record({ + key: 'tid', + record: lx.object({ + subject: lx.string({ + format: 'at-uri', + required: true, + description: 'AT-URI of the work.sequential.page, work.sequential.slice or work.sequential.strip being annotated. AppViews ignore annotations on any other record. Use the DID form (at://did:...), not a handle.' + }), + x: lx.integer({ + minimum: 0, + maximum: 10000, + required: true, + description: 'Horizontal position of the annotation, in ten-thousandths of the image\'s width (0-10000), from the left.' + }), + y: lx.integer({ + minimum: 0, + maximum: 10000, + required: true, + description: 'Vertical position of the annotation, in ten-thousandths of the image\'s height (0-10000), from the top.' + }), + text: lx.array(lx.ref('work.sequential.defs#localizedText'), { + maxLength: MAX_LANGUAGES, + minLength: 1, + required: true, + description: 'Localized annotation text.' + }), + createdAt: lx.string({ + format: 'datetime', + required: true, + description: 'The date and time the annotation was created.' + }) + }), + description: 'A note pinned to a point on a page, slice or strip. Anyone can write one in their own repo; AppViews decide whose annotations to show, e.g. only those from the repo that owns the subject.' + }) +}) -- 2.51.2