Skip to content

Credit Scripture versions on translated slides #181

Open

Description

@kcarnold

Context

The slide agent (draftItemTranslations / runSlideTranslationAgent in nlp.ts) grounds
Scripture slides in published Bibles fetched through lookup_bible_passage (bible.ts,
BIBLE_TRANSLATIONS). Nothing records which published text a slide came from, so viewers
never see a credit. Some of these texts carry licenses that require one; for example, hatbsa
is CC BY-SA 4.0 (ebible.org/Scriptures/details.php?id=hatbsa). The goal is a small,
unobtrusive credit under the translated slide, shown only when the slide reproduces Scripture
(at least about one verse). A phrase borrowed for a song gets no credit.

Decisions made: the model declares the credit, and the viewer shows version + license
only
(no reference). On the base: it was "stack on PR 152", but 169/170 now move the
pipeline too (see below). You expect 169/170 to land soon, so: build on main after 169/170. If 152 is still
open then, stack on 152 (rebased onto that main), since it owns the entry shape and the notes
UI this extends.

Design

1. Sources registry: src/textSources.ts (new, pure)

  • TextSource = { id, kind: 'scripture' | 'creed', name, publisher, license, licenseUrl, sourceUrl }
  • TEXT_SOURCES: Record<string, TextSource>, keyed by helloao id (hatbsa, fra_ncl, spa_r09).
    Creeds and confessions are added here later under kind: 'creed', with no schema change.
  • formatCredit(source) returns e.g. Bib la — Société Biblique Haïtienne · CC BY-SA 4.0.
  • Entries store the id, not the rendered string. If a credit turns out wrong, we fix the
    registry once and every stored slide picks up the fix.
  • bible.ts keeps BIBLE_TRANSLATIONS, and a test asserts that every id in it has a
    TEXT_SOURCES entry. Adding a Bible without its credit then fails CI.

2. Data model (on top of PR 152)

  • SlideTranslationEntry in src/slideTranslation.ts gets optional sourceId?: string and
    adapted?: boolean; both ride the same path below.
  • It is threaded through TranslationBlockResult (nlp.ts) → translateItem
    (src/slideItemTranslation.ts) → the returned entries → slideLibrary.ts (persist, and keep
    it through the record rebuild that PR 152 added) → the /api/slideLibrary upsert body.
  • slide_translator.py _store_translations: copy sourceId when present.

3. Model declares it: nlp.ts

  • Add two optional fields to the set_translations segment schema, both named generically:
    • source, an enum. It is 'bible' today and gains 'creed' once a creed lookup tool
      exists. The description gives the rule: set only when this slide reproduces at least
      one full verse (or sentence) of text you looked up; omit it for allusions, short phrases,
      and song lyrics.
    • adapted, a boolean: whether the slide alters the published wording. We trust the agent
      on this.
  • The model doesn't echo a reference. I checked what a lookup returns:
    { reference: "PSA 23", passages: { French: … } }, where the reference is the requested
    range
    . That is often wider than one slide (a chapter lookup, with the slide holding verses
    1–3). So echoing it would be loose matching at best. It also isn't needed: the credit
    depends only on which version, and that's fixed by the language (BIBLE_TRANSLATIONS).
  • Server-side guard: attach sourceId = BIBLE_TRANSLATIONS[language] only if the
    conversation messages contain a lookup_bible_passage response that returned a passage
    in that language. Scanning messages, rather than a per-run list, covers follow-up runs
    whose lookup happened in an earlier round. The model can't name a license, and it can't
    get a credit for a version it never fetched.
  • Store sourceId (and adapted) at write time, so a later change to BIBLE_TRANSLATIONS
    doesn't relabel old slides.
  • Working-copy semantics follow PR 152's notes: a rewrite without source withdraws the
    credit, and revise_translation leaves it alone.
  • One sentence goes in bibleSection of buildSlideTranslationPrompt pointing at the fields.

4. Viewer: src/SlideTranslationViewer.tsx

  • When resolved.entry.sourceId maps to a source, render a footer line through SlideText.
    Add a footer prop next to header if SlideText lacks one. Style it text-xs, low
    opacity, and keep it outside the fit-text measurement so it doesn't shrink the slide.
  • The license name links to licenseUrl. This matters for phone viewers; on the projector it
    is just text.
  • No label for a verbatim quote: the line itself (version · publisher · license) is
    self-explanatory. There is one localized string, adaptedFrom ("adapted from" / "adapté
    de" / …), in src/strings.ts for all four locales. It is used only when adapted is true.
    It follows the viewer's UI locale like every other string.

5. Review screen: src/SlideReview.tsx

  • Show the same credit line in the cell, next to PR 152's note, so the reviewer can see the
    claim. A × to clear it is optional and goes in only if it turns out trivial.

6. Follow-up issue (not built)

  • "Require a Scripture credit on BiblePassage items". itemKind already reaches
    proclaimPresentations (yjs_publisher.py) but is not sent to /api/translateItem
    (only itemTitle is, from proclaim_service.py and src/slideTranslationApi.ts). Per your
    answer, this goes to an issue. Also include: the source-language credit (Proclaim's own
    English version on currentSlide), and localized references if that's wanted later.

If PRs 169/170 land first (server-owned slide sync)

Most of the plan is untouched. 169 doesn't change nlp.ts (the agent, its tools, the
prompt), the viewer, the review screen, or slideLibrary.ts. The changes are:

  • slide_translator.py is deleted. Its entry write moves to writeSlideTranslations in
    slideSync.ts, so sourceId/adapted get copied there. Python is no longer involved.
  • One entry point instead of two. Both /api/translateItem and the server's
    translate-ahead now go through draftItem in server.ts, so the credit is threaded once.
  • The BiblePassage rule becomes cheap. The snapshot now carries itemKind to the server
    (slideSync.ts), and proclaimPresentations already stores it for the review screen. So
    passing itemKind into draftItem → buildSlideTranslationPrompt is a few lines: "this
    item is a Bible passage; every slide must declare source: 'bible'". This moves from
    follow-up issue into scope.
  • Ordering conflict to watch: 169 still skips status === 'reviewed' entries, and 152
    deletes status. Whichever lands second has to reconcile that. This work is cleaner on
    main after both than stacked on either one.

Things to settle while implementing (flagged, not assumed)

  • Registry values (supplied by you; hatbsa's publisher name still gets copied verbatim
    from its ebible page):
    • fra_ncl: Sainte Bible néo-Crampon Libre, © 2022 Fraternité de Tibériade, CC BY-SA 4.0.
      Its terms say an adapted text must indicate the change, which the adapted → "adapted
      from" line covers.
    • spa_r09: Reina-Valera 1909, public domain. No credit is legally required, but we
      still show "Reina-Valera 1909 · dominio público". Crediting only the licensed versions
      would read as inconsistent to a viewer, and the line costs nothing.
    • hatbsa: Bib la (Haitian Bible Society), CC BY-SA 4.0.
  • ShareAlike. Under CC BY-SA an adapted slide is itself under CC BY-SA. The credit line
    already names that license, so nothing extra is shown, but it's worth one line in the PR.

Files

src/textSources.ts (new), bible.ts, nlp.ts, src/slideTranslation.ts,
src/slideItemTranslation.ts, slideLibrary.ts, server.ts (upsert body),
slide_translator.py (or, after 169, slideSync.ts + draftItem in server.ts),
src/SlideTranslationViewer.tsx, src/SlideText.tsx,
src/SlideReview.tsx, src/strings.ts, and a short note in docs/slide-translations-plan.md
in PR 152's amendment style.

Verification

  • Unit tests: registry covers BIBLE_TRANSLATIONS; source: 'bible' attaches sourceId
    when the history holds a lookup for that language (including one from an earlier round),
    and is dropped when it doesn't; a rewrite without source withdraws it; adapted renders
    "adapted from"; slideLibrary round-trips sourceId; the viewer renders the footer
    only when sourceId resolves; Python _store_translations copies it.
  • npm run typecheck, npm run lint, npm test -- --no-color, uv run pytest.
  • Smoke §3 (Proclaim sync): replay tests/fixtures/synthetic_service.jsonl and confirm a
    Scripture item shows the credit and a song doesn't.
  • Push to claude/vibrant-albattani-lk70bu based on PR 152's head, and open a PR noting it
    stacks on PR 152.

Originally posted by @kcarnold in #152 (comment)

Activity

  1. kcarnold commented on Oct 26, 2026

    @kcarnold
    CollaboratorAuthor

    Two updates from splitting #152:


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions