feat(core): auto-transform Bible HTML in getPassage - #216
Conversation
getPassage now automatically sanitizes and transforms HTML content
before returning — verse wrapping, footnote extraction, nbsp, and
table fixes all happen at the root. Uses native DOMParser in browser,
dynamic import('linkedom') on server. Added data-yv-transformed
idempotency marker so double-transforms are a no-op.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 1948fac The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Run XSS sanitization before idempotency check so data-yv-transformed cannot bypass sanitizeBibleHtmlDocument. Add clear error message when linkedom is missing on server instead of opaque module-not-found error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
davidfedor
left a comment
There was a problem hiding this comment.
Big picture: I love the idea of being helpful, without requiring the developer to have to make another call. My comments and questions are around whether this is the best way to do that. (Maybe it is! I'm not sure yet.)
I notice this would be blurring the lines between Core being merely an API helper-layer, but now it would be doing some of the prep-work of the UI (visualization layer). So at the least having that be optional seems wise.
I'm wondering if that parameter should default to do the transformation, or not... or whether we need to force the dev to make a choice (to attempt to force them to make an informed choice).
|
(FYI I've asked for thoughts from Bryson H; not sure if he's got cycles to contribute or not) |
Add `transform` param to `getPassage` (default: true) so consumers can receive untransformed HTML without needing linkedom on the server. CSS now handles verse label spacing for raw HTML via ::after pseudo-element. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019df363-8170-750b-866b-d30055111f9b Co-authored-by: Amp <amp@ampcode.com>
|
@davidfedor @cameronapak this looks good to me, I just don't see an "approve" button in my UI. Approved by me! |
|
Some added context on this ticket is: Why transformBibleHtml Exists — And Where It May Not Be Needed If we can get this merged in, then I can write better docs for helping people use our HTML and styles without them having to manually |
|
Adding some clarity. David sent me a DM on Teams the other day saying this:
|
jsdom fails at runtime on Cloudflare Workers after a clean bundle. linkedom covers the transform DOM surface, runs in workerd, and keeps the optional peer small. Consolidate adapters and add a workerd CI smoke so the next peer swap cannot silently break edge. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
Aligns runtime jsdom with @types/jsdom@^28 so Greptile stops flagging the major skew. jsdom is test-only here (vitest + tbody browser stand-in). Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
Regenerate pnpm-lock.yaml from main rather than hand-merging. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
Point the workerd CI smoke at resolveHtmlAdapters + transformBibleHtml so a Node-only import or broken dynamic linkedom load fails the gate. Surface the original import error alongside the install guidance. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
Cam requested dropping the workerd CI gate; linkedom remains the runtime peer and jsdom stays test-only for vitest/browser reparse. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
Revert the linkedom runtime choice per Cam — no workerd target. Keep native DOMParser in browser and dynamic jsdom on server. Drop bible-html-adapters and realign docs/changeset. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
…r only on root Stub jsdom via package.json browser field so Vite/Rollup examples do not bundle cssstyle. Only short-circuit idempotency when data-yv-transformed is on the transform root; tighten the onclick sanitizer expect. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
UI tsup was bundling platform-core's jsdom import into the React package (~11MB) and breaking vite-react. Mark jsdom external and stub it via the browser field; mirror external in core's tsup. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com> Signed-off-by: Cameron Pak <cameronandrewpak@gmail.com>
The idempotency guard checked only the first top-level element. A fragment that mixed transformed and raw roots skipped verse wrapping and footnote extraction for the raw siblings. Check every top-level element instead, and mark every top-level element on transform so the marker means "this whole fragment came from here". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BibleClient.getPassage has a transform escape hatch for skipping HTML transformation, but usePassage dropped it. Callers who want raw HTML, or who run without the optional jsdom peer in a non-DOM environment, had no way to ask for it through the hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Jeff Hampton <jhampton@gmail.com>
|
@davidfedor - the Idempotency guarantee you mentioned was already implemented and Greptile shows 5/5 - is there another place I'm not seeing? |
Summary
Auto-transforms Bible HTML inside
getPassageso consumers never need to calltransformBibleHtmlmanually. Uses nativeDOMParserin the browser and dynamicimport('jsdom')on the server. Addeddata-yv-transformedidempotency marker so double-transforms are a no-op.Runtime DOM choice: optional peer is
jsdom(not linkedom). No workerd-specific path or CI smoke — server consumers that need auto-transform installjsdom, or passtransform: falseto skip.Verse.Htmlretains itstransformBibleHtmlcall as defense-in-depth — the idempotency marker makes it a no-op for HTML that already went throughgetPassage.Node floor:
jsdom@28.1.0(devDep / peer^24 || ^28) declares^20.19.0 || ^22.12.0 || >=24.0.0, which covers the repoengines.node >=22.13floor (Decision 3).Context: Why transformBibleHtml Exists — And Where It May Not Be Needed
Test plan
getPassagewithformat: 'html'returns transformed content (data-yv-transformedpresent)getPassagewithformat: 'text'returns raw content (no transformation)data-verse-footnoteattributesVerse.Htmlstill sanitizes raw HTML passed directly (XSS protection)Greptile Summary
The PR makes
getPassageautomatically sanitize and structurally transform HTML while retaining explicit opt-out and runtime-specific DOM behavior.jsdomadapters.usePassage.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains, and the previously reported marker, dependency-error, type-version, sibling-root, and hook-forwarding issues are fixed or invalid at the current head.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Consumer participant Hook as usePassage participant Core as BibleClient.getPassage participant API as YouVersion API participant DOM as DOMParser or jsdom Consumer->>Hook: Request passage with transform option Hook->>Core: Forward request and transform option Core->>API: Fetch passage API-->>Core: Passage content alt HTML and transform enabled Core->>DOM: Parse content DOM-->>Core: Document Core->>Core: Sanitize and structurally transform end Core-->>Hook: Passage Hook-->>Consumer: Passage stateReviews (18): Last reviewed commit: "Merge branch 'main' into transform-bible..." | Re-trigger Greptile
Context used (3)
@youversion/platform-core)