Skip to content

feat(a11y): image alt text via NIP-92 imeta across all post types - #746

Merged
spe1020 merged 4 commits into
zapcooking:mainfrom
dmnyc:feat/imeta-alt-text
Sep 21, 2026
Merged

spe1020 merged 4 commits into
zapcooking:mainfrom
dmnyc:feat/imeta-alt-text

Conversation

@dmnyc

@dmnyc dmnyc commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds read + author support for image alt text using NIP-92 imeta tags, matching the wire behavior Amethyst and Gossip already implement. Works kind-agnostic: notes, replies/comments, recipes (create/fork/gated), longform article covers, and marketplace products.

Verified interop: parses and displays alt from real Amethyst-published notes (locked in as a regression test with a live relay event), and emits tags that Amethyst/Gossip/Damus render.

Read path

  • imetaAltByUrl() builds a url→alt map per event; alt renders as img alt across feed, note detail, lightbox, and profile media grid
  • Visible ALT badge on described images opens a "Description" dialog; fullscreen viewer shows alt as a bottom caption
  • Badge is a sibling of the image tap target (no nested buttons); screen readers get two clean stops

Compose path

  • Shared AltTextEditorModal ("+ ALT" → "✓ ALT" chip flow, 2000-char cap, per-URL persistence in drafts)
  • Wired into: post composer, reply composer, recipe editors via MediaUploader, longform cover, product form
  • Publish emits one imeta tag per described image; no empty metadata; newlines flattened; existing imeta slots preserved on fork/edit

AI generation (Cook+)

  • POST /api/zappy/ask-photo gains purpose: "alt": neutral describer instruction, no food gate (alt must describe any image), temperature 0.4, member question ignored
  • Same NIP-98 auth, fail-closed membership gate (403 NOT_MEMBER → upsell), and 8/hr + 30/day per-pubkey rate limits as Cheffy photo asks
  • "Generate with AI (Cook+)" action fills the field as an editable draft

Tests

  • 3 new ask-photo alt-mode tests (instruction selection, NOT_FOOD pass-through, temperature)
  • imeta suite extended to 27 tests incl. build→parse round-trip and the real Amethyst event
  • Full suite: 1988 passing

Mobile handoff

docs/accessibility/alt-text-imeta-handoff.md contains the wire-format spec, UX conventions, AI endpoint contract, test vector, and a rollout checklist for the Android and iOS versions.

Read, display, and author alt text for images using NIP-92 imeta
tags, matching the wire behavior of Amethyst and Gossip.

Read:
- parse imeta url->alt map (imetaAltByUrl) and render as img alt
- ALT badge on media tiles opens a Description dialog
- fullscreen viewer shows alt as a bottom caption
- profile media grid and lightbox carry alt through

Compose:
- shared AltTextEditorModal (+ ALT / check-ALT chips, 2000-char cap,
  drafts persist alt keyed by URL)
- wired into post composer, reply composer, recipe create/fork/gated
  (MediaUploader), longform cover, and marketplace product form
- publish emits one imeta tag per described image; no empty metadata

AI (Cook+):
- ask-photo gains purpose:"alt" — neutral describer instruction, no
  food gate, temperature 0.4, member question ignored
- Generate-with-AI action in the editor; membership + rate limits
  reuse the existing Cheffy photo pipeline

Includes an Amethyst-published event locked in as an interop
regression test and a mobile handoff spec in
docs/accessibility/alt-text-imeta-handoff.md.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate issues affect metadata preservation, draft persistence, rendering, and accessibility.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 8 Medium severity

Open (9)
What changed in this PR

Adds NIP-92 imeta image alt-text support across publishing, rendering, marketplace content, AI generation, and mobile handoff documentation.

Changes:

  • Adds alt-text parsing, editing, badges, dialogs, captions, and lightbox support.
  • Integrates alt-text publishing and draft handling across posts, recipes, articles, replies, and products.
  • Adds Cook+ AI alt generation, tests, interoperability coverage, and documentation.
File Description
src/​routes/​user/​[slug]/​+page.svelte Adds profile media alt text.
src/​routes/​fork/​[slug]/​+page.svelte Preserves alt text when forking recipes.
src/​routes/​create/​gated/​+page.svelte Emits gated recipe alt metadata.
src/​routes/​create/​+page.svelte Emits recipe alt metadata.
src/​routes/​api/​zappy/​ask-photo/​askPhoto.test.ts Tests AI alt mode.
src/​routes/​api/​zappy/​ask-photo/​+server.ts Implements alt-generation API mode.
src/​lib/​photoAsk.ts Sends alt-generation requests.
src/​lib/​noteReview.test.ts Updates accessibility assertions.
src/​lib/​marketplace/​types.ts Adds product alt-text state.
src/​lib/​marketplace/​products.ts Emits product imeta tags.
src/​lib/​feed/​imeta.ts Parses and builds alt metadata.
src/​lib/​feed/​imeta.test.ts Tests alt metadata interoperability.
src/​lib/​cheffyPrompt.server.ts Adds neutral alt-text prompting.
src/​components/​reads/​LongformEditorModal.svelte Adds article cover alt editing.
src/​components/​PostComposer.svelte Adds post alt editing and persistence.
src/​components/​NoteContent.svelte Applies alt text to note media.
src/​components/​MediaUploader.svelte Adds uploader alt controls.
src/​components/​MediaLightbox.svelte Adds lightbox captions.
src/​components/​MediaCarousel.svelte Adds alt badges and descriptions.
src/​components/​marketplace/​ProductForm.svelte Connects product alt state.
src/​components/​FoodstrFeedOptimized.svelte Passes alt text into feed media.
src/​components/​comments/​ReplyComposer.svelte Adds reply alt editing.
src/​components/​AltTextEditorModal.svelte Provides manual and AI alt editing.
package.json Bumps the application version.
docs/​accessibility/​alt-text-imeta-handoff.md Documents web/mobile alt-text integration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/routes/fork/[slug]/+page.svelte Outdated
Comment thread src/components/MediaCarousel.svelte
Comment thread src/components/MediaUploader.svelte Outdated
Comment thread src/components/marketplace/ProductForm.svelte
Comment thread src/components/reads/LongformEditorModal.svelte Outdated
Comment thread src/components/reads/LongformEditorModal.svelte
Comment thread src/lib/marketplace/products.ts
Comment thread src/routes/create/+page.svelte
Comment thread src/routes/fork/[slug]/+page.svelte Outdated
spe1020 and others added 3 commits September 20, 2026 20:48
…ork slots

Resolves the Copilot review findings on zapcooking#746:

- fork: imetaAltByUrl returns a Map; convert to the Record the editor
  reads. Preserve the source imeta row (m, dim, blurhash, x, fallback)
  and only replace the alt slot via new withImetaAlt/imetaTagsByUrl.
- MediaCarousel: .media-tile is now position: relative so each ALT
  badge overlays its own tile instead of stacking on the gallery.
- MediaUploader: additional-media tiles no longer nest buttons — the
  make-cover action is a sibling overlay button beside remove and alt.
- Marketplace: parseProductEvent carries imeta alt into Product; the
  edit route seeds the form so saving doesn't strip it; ProductCard and
  ProductViewModal render it.
- Longform: cover alt is persisted in ArticleDraft.coverAlts keyed by
  cover URL, included in the dirty-state hash, and read for the current
  URL on publish.
- Recipe.svelte (recipes + article covers): carousel and lightbox use
  the imeta alt map with a visible caption in the lightbox.
- Recipe drafts: RecipeDraft.imageAlts round-trips through edit
  (Recipe.svelte → /create) and draft save/load on create + gated.
- AltTextEditorModal: fix maxLength prop errors flagged by svelte-check.

svelte-check: 0 errors. vitest: 1994 passing (6 new imeta tests).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings the reader-side convention from the feed carousel to
Recipe.svelte: images with imeta alt text show an ALT badge (a sibling
of the image button, no nested controls) that opens the description.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@spe1020
spe1020 merged commit 8a651e9 into zapcooking:main Sep 21, 2026
5 checks passed
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.

3 participants