Skip to content

fix(a11y): preserve line breaks in imeta alt text, capped at one blank line - #749

Open
dmnyc wants to merge 1 commit into
zapcooking:mainfrom
dmnyc:fix/imeta-alt-linebreaks
Open

dmnyc wants to merge 1 commit into
zapcooking:mainfrom
dmnyc:fix/imeta-alt-linebreaks

Conversation

@dmnyc

@dmnyc dmnyc commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Problem

The ALT editor is a multiline textarea — we invite multi-paragraph descriptions — but every publish path flattened the alt slot's \n to a space. Authored paragraphs were silently destroyed at publish, for every reader including our own (verified against a real published note, id 3f66caf7…).

Fix

  • normalizeAltBreaks (new, src/lib/feed/imeta.ts): CRLF → LF, trims each line, caps runs of 3+ newlines at exactly \n\n (one blank-line paragraph gap), trims the ends.
  • Publish paths preserve line breaks instead of flattening: buildImetaTag (notes, replies, recipes, forks, longform covers, products) and withImetaAlt (edit/fork tag round-trip).
  • Read paths cap (parseImeta, imetaAltByUrl): alt received from any client can't balloon the layout.
  • Display: the fullscreen lightbox caption gets white-space: pre-line (the Description dialog already used pre-wrap).

Wire format example

NIP-92 slots are "key + everything after the first space", so a real \n inside the tag string survives the round trip: JSON escapes it on the wire, relays store it verbatim, event ids/signatures are unaffected. Built with the shipping code and parsed back byte-identical:

[
  "imeta",
  "url https://i.nostr.build/eS5EHNjnkLY9cJ4iQ47pOO.png",
  "alt A screenshot of a Nostr post about web accessibility.\n\nBelow it, a quoted post from @BlindBitcoiner: \"Hello world this is a test post. I'm totally blind and trying Damus to see if blind users can use this Nostr client.\""
]

Pre-fix, the same input published as a single flattened line.

Interop note

NIP-92 is silent on newlines; this adopts the no-invention option (raw newlines, no escape scheme). Clients parsing slots on the first space get the full multi-paragraph text; clients splitting on any whitespace see only the first line — a degraded but valid description, versus today's loss for everyone.

Mobile parity

docs/accessibility/alt-text-imeta-linebreaks-mobile-handoff.md is an agent-facing spec for the Android/iOS apps: normative rules, reference normalization in TS/Kotlin/Swift sketches, parse requirements (first-space split), display/authoring rules, and test vectors. §1 of the main handoff doc (alt-text-imeta-handoff.md) is updated to the new wire contract.

Tests

Flatten assertions replaced with preserve/cap/CRLF/JSON-round-trip/received-cap vectors (38 imeta tests) ; the Amethyst interop vector is unchanged and passing. noteReview suite green (71).

…k line

The ALT editor is multiline, but every publish path flattened the alt
value's newlines to spaces — multi-paragraph descriptions were silently
destroyed at publish.

- normalizeAltBreaks: CRLF → LF, trim lines, cap runs of 3+ newlines
  at exactly two (one blank-line paragraph gap)
- buildImetaTag / withImetaAlt now preserve line breaks (covers notes,
  replies, recipes, forks, longform covers, products, and the
  edit/fork tag round-trip)
- read paths (parseImeta, imetaAltByUrl) apply the same cap to alt
  received from any client, so runaway gaps can't balloon the layout
- lightbox caption renders breaks (white-space: pre-line); the
  Description dialog already did
- line breaks ride as real \n in the tag string: NIP-92 slots are
  'key + everything after the first space', so they survive the wire
  (JSON escapes them) and re-parse byte-identical

Tests: flatten assertions replaced with preserve/cap/CRLF/round-trip
vectors; the Amethyst interop vector is unchanged and passing.
Mobile parity spec: docs/accessibility/alt-text-imeta-linebreaks-mobile-handoff.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant