JSON-Rohbericht maschinenlesbar
{
"data": {
"repo": {
"topics": [
"epub",
"epub-reader",
"epub3",
"epubjs",
"npm",
"typescript"
],
"is_fork": false,
"size_kb": 2580,
"has_wiki": false,
"homepage": "https://likecoin.github.io/epub.ts/",
"languages": {
"HTML": 839,
"JavaScript": 21918,
"TypeScript": 821003
},
"pushed_at": "2026-07-17T23:23:09Z",
"created_at": "2026-02-06T10:41:07Z",
"owner_type": "Organization",
"updated_at": "2026-07-17T23:23:07Z",
"description": "A TypeScript fork of epubjs — parse and render EPUB documents in the browser.",
"is_archived": false,
"is_disabled": false,
"license_spdx": null,
"default_branch": "master",
"license_spdx_raw": "NOASSERTION",
"primary_language": "TypeScript",
"significant_languages": [
"TypeScript"
]
},
"owner": {
"blog": "https://like.co",
"name": "LikeCoin",
"type": "Organization",
"login": "likecoin",
"company": null,
"location": null,
"followers": 70,
"avatar_url": "https://avatars.githubusercontent.com/u/6298090?v=4",
"created_at": "2014-01-01T17:04:47Z",
"is_verified": null,
"public_repos": 166,
"account_age_days": 4585
},
"license": {
"state": "custom",
"spdx_id": null,
"raw_spdx": "NOASSERTION",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v0.6.9",
"kind": "patch",
"published_at": "2026-07-17T23:23:47Z"
},
{
"tag": "v0.6.8",
"kind": "patch",
"published_at": "2026-07-05T09:31:39Z"
},
{
"tag": "v0.6.7",
"kind": "patch",
"published_at": "2026-06-19T16:39:10Z"
},
{
"tag": "v0.6.6",
"kind": "patch",
"published_at": "2026-05-27T10:59:27Z"
},
{
"tag": "v0.6.5",
"kind": "patch",
"published_at": "2026-05-22T11:45:06Z"
},
{
"tag": "v0.6.4",
"kind": "patch",
"published_at": "2026-05-19T09:45:20Z"
},
{
"tag": "v0.6.3",
"kind": "patch",
"published_at": "2026-04-14T09:49:08Z"
},
{
"tag": "v0.6.2",
"kind": "patch",
"published_at": "2026-04-10T16:03:20Z"
},
{
"tag": "v0.6.1",
"kind": "patch",
"published_at": "2026-04-08T15:28:45Z"
},
{
"tag": "v0.6.0",
"kind": "minor",
"published_at": "2026-04-01T17:22:52Z"
},
{
"tag": "v0.5.1",
"kind": "patch",
"published_at": "2026-03-30T08:32:24Z"
},
{
"tag": "v0.5.0",
"kind": "minor",
"published_at": "2026-03-27T19:28:14Z"
},
{
"tag": "v0.4.9",
"kind": "patch",
"published_at": "2026-03-03T16:53:42Z"
},
{
"tag": "v0.4.8",
"kind": "patch",
"published_at": "2026-03-03T07:44:24Z"
},
{
"tag": "v0.4.7",
"kind": "patch",
"published_at": "2026-02-26T16:27:21Z"
},
{
"tag": "v0.4.6",
"kind": "patch",
"published_at": "2026-02-20T04:37:52Z"
},
{
"tag": "v0.4.5",
"kind": "patch",
"published_at": "2026-02-16T21:40:12Z"
},
{
"tag": "v0.4.4",
"kind": "patch",
"published_at": "2026-02-16T06:55:21Z"
},
{
"tag": "v0.4.3",
"kind": "patch",
"published_at": "2026-02-15T10:05:43Z"
},
{
"tag": "v0.4.2",
"kind": "patch",
"published_at": "2026-02-14T18:19:49Z"
},
{
"tag": "v0.4.1",
"kind": "patch",
"published_at": "2026-02-10T19:49:04Z"
},
{
"tag": "v0.4.0",
"kind": "minor",
"published_at": "2026-02-10T18:57:21Z"
},
{
"tag": "v0.3.97",
"kind": "patch",
"published_at": "2026-02-09T17:15:06Z"
},
{
"tag": "v0.3.96",
"kind": "patch",
"published_at": "2026-02-06T20:50:40Z"
},
{
"tag": "v0.3.95",
"kind": "patch",
"published_at": "2026-02-06T19:49:30Z"
},
{
"tag": "v0.3.94",
"kind": "patch",
"published_at": "2026-02-06T19:48:50Z"
},
{
"tag": "v0.3.93",
"kind": "patch",
"published_at": "2026-02-06T19:29:05Z"
}
],
"recent_commits": [
{
"oid": "d2eaa1be3bc004ab5405e68ff748ffac70f2782b",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.9",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T23:22:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6e3700bda640178f6aa893781339691685e96c32",
"body": "Guard against configuredDOMParser and globalThis.DOMParser both being\nabsent (e.g. Node without the /node entry and without setDOMParser), so\ncallers get an actionable EpubError instead of a \"ParserCtor is not a\nconstructor\" TypeError. Addresses PR review feedback.",
"is_bot": false,
"headline": "🐛 Fail clearly in parse() when no DOMParser is available",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T21:16:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "40bd43b82feb28f4b84c3109e13b385110c71242",
"body": "Section.load inferred the parse type solely from the URL extension, so an\nextensionless or oddly-named spine resource (e.g. chapter.html_split_001)\nfell through to a raw string and Section.load read documentElement off it,\nthrowing in the content hooks. Propagate the manifest media-type to Section\nand fall back to it when the extension yields no known parse token. A\nrecognized extension still wins, so lenient html vs strict xhtml parsing is\nunchanged for normal books.",
"is_bot": false,
"headline": "✨ Parse spine resources by manifest media-type when extension is unknown",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T20:48:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "99471896c33f82e904489abbe252f21067d3a0e2",
"body": "findUniqueIdentifier required localName \"identifier\" in the Dublin Core\nnamespace, but linkedom keeps the \"dc:\" prefix on the local name and\nmis-assigns the namespace, so prefixed EPUB 2 books came back with an\nempty uniqueIdentifier. Strip any prefix and also accept a dc:-prefixed\ntag, and read textContent so entity references don't truncate the value.",
"is_bot": false,
"headline": "🐛 Resolve uniqueIdentifier under namespace-blind parsers",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T20:30:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ff493b6d8d16cbd212ccf77289dbd3bb8f34111b",
"body": "On the Node/linkedom path, an entity reference becomes its own child node,\nso \"Bookshops & Bonedust\" parses as [\"Bookshops \", entity, \" Bonedust\"].\ngetElementText/getPropertyText read only childNodes[0], truncating the value\nat the first entity (\"Bookshops\"). Reading textContent concatenates and\ndecodes the whole value. Browsers keep it a single text node, so their output\nis unchanged.",
"is_bot": false,
"headline": "🐛 Read metadata via textContent so entities don't truncate values",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T20:09:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0919de07da7765d71ddc7dbda6f2699e7e1d0506",
"body": "Legacy EPUB 2 packages that use prefixed element names (<opf:metadata>,\n<opf:manifest>, <opf:item>, <opf:itemref>, …) open in the browser but\nreject with \"No Metadata Found\" on the Node/linkedom path: linkedom has\nno XML namespace support, so it keeps the literal \"opf:\" on tag names and\nthe unprefix\n[…]\namespace-aware parsers match on the\nfirst try, so their behavior is unchanged. Mirrors the existing dc:\nfallback in getElementText and lets consumers drop the whole-EPUB opf:\nnormalization workaround.",
"is_bot": false,
"headline": "🐛 Match prefixed OPF elements under namespace-blind parsers",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T20:05:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "da218607899870b2ec0e3de121b725baed10b3fc",
"body": "Some real-world EPUBs drive LinkeDOM's synchronous parser into a busy\nloop that never returns, forcing Node consumers to isolate each book in\na subprocess. Add setDOMParser (and a BookOptions.domParser shortcut) so\njsdom can be swapped in without monkey-patching globalThis.DOMParser.\n\nThe override is process-global, consistent with how the parse path\nalready reads the global DOMParser, and covers both book-open and\nrender parsing since both funnel through core.parse.",
"is_bot": false,
"headline": "✨ Allow injecting a DOMParser to sidestep LinkeDOM hangs",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-17T19:42:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ce5ca40f5870335b30a50d0e41808ea71121a102",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.8",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-05T09:28:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4b55ba8a8ba8d4f386180590fa7acac81b07192b",
"body": "The container recovery callback re-read layout via getBoundingClientRect()\non every fire — a forced reflow inside a ResizeObserver callback, which can\ntrigger further resize notifications. Use the contentRect the observer\nalready delivers instead, and centralize the disconnect/clear dance in a\n_disconnectContainerObserver() helper (matching _disarmReanchor) so the\ncallback and destroy() share one idiom.",
"is_bot": false,
"headline": "♻️ Read container size from ResizeObserver entry, not a reflow",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-05T09:23:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dc44523474fef874027bab52f9dffe9a51e583b9",
"body": "A rendition displayed into a display:none / zero-size element has no\nvisible views, so reportLocation() can't emit and relocated/\nlocationChanged never fire. The stage only listens to window \"resize\"\n(and only for non-fixed sizes), so later revealing the container never\nre-reports — a common react-n\n[…]\ny on a genuine unmeasurable→measurable transition (so a\ncontainer sized from the start doesn't double-emit) and only until the\nfirst location is established, after which the observer self-disconnects.",
"is_bot": false,
"headline": "🐛 Re-report location when a hidden container becomes visible",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-05T09:11:28Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1a5fbf0296f315bf8dcb668583aafe81759d961f",
"body": "Book open failures masked the real error behind a generic \"Cannot load\nbook\" message and never rejected `opening`/`loading`, so `book.opened`\nand `book.ready` hung forever instead of surfacing the cause (e.g.\n\"JSZip lib not loaded\" when the UMD bundle is loaded without JSZip).\n\nEmit the underlying e\n[…]\ng the raw\nvalue (a base64 string is the whole book; binary inputs stringify to\n\"[object …]\"). Add regression tests covering the rejected promises, the\nopenFailed event, and the synchronous-throw path.",
"is_bot": false,
"headline": "🐛 Surface real cause and fail fast on book open error",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-05T08:35:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d1c79eaaf014641b37382ea48d11ea0eff129547",
"body": "The XHR fallback (used for file:// URLs and fetch-less runtimes) hit a\nbare new XMLHttpRequest() when the runtime also lacked XMLHttpRequest —\ne.g. Node without a polyfill — throwing a cryptic \"is not a constructor\".\nReject with a clear EpubError instead, and cover it with a test.",
"is_bot": false,
"headline": "🐛 Fail clearly when XMLHttpRequest is unavailable",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-04T18:53:33Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bbb81b2e8ee2c462f679f20743e312b6914469f5",
"body": "Add a ./umd export and a ./dist/* glob so the existing UMD build is\nreachable, and document the <script>-tag / react-native-webview drop-in\nalongside file:// loading notes. In a bundler, point at the standard\nESM entrypoint rather than the UMD subpath.",
"is_bot": false,
"headline": "📦 Expose UMD bundle via ./umd export and document WebView drop-in",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-04T18:53:33Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8d83c45b082c1b2b50e6b38c4a675e989a5c42db",
"body": "fetch() cannot load the file: scheme, so a local .epub in a\nreact-native-webview or file:// deployment never loads — and since\nnothing renders, relocated/locationChanged never fire. Route file://\nURLs (and fetch-less runtimes) through XMLHttpRequest, treating status 0\n(file: only) as success and bri\n[…]\nhandleResponse/createBlob from core and DOMException for the abort\nrejection; reject if handleResponse throws, and fail with a clear\nEpubError when XMLHttpRequest is unavailable (e.g. the Node build).",
"is_bot": false,
"headline": "✨ Read file:// books via XMLHttpRequest fallback",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-07-04T18:53:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "91e8b7562c65459ae7aad4279e0c09063af0dc3d",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.7",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-06-19T16:37:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7a8a240656584e21dfd673ceab5140d5a1e738ce",
"body": "reportLocation schedules a requestAnimationFrame whose callback can fire\nafter destroy() has set this.manager = undefined, throwing \"Cannot read\nproperties of undefined (reading 'currentLocation')\". Bail out of the rAF\ncallback when the manager is gone, and add the same guard to the public\ncurrentLocation() method, which reaches the identical call synchronously.",
"is_bot": false,
"headline": "🐛 Guard currentLocation against destroyed manager",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-06-19T16:35:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1ee3f64d9453adda9fe2058237709c34b4808db4",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.6",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-27T10:56:03Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "853dad8ccb4984602dc1f14e18e8ce8fe9c0324d",
"body": "Image-only or whitespace-only sections previously produced zero\nlocations, skewing Locations.total and every progress percentage after\nthem. Create the opening range before skipping empty nodes so the\ntrailing range still emits one location.\n\nPorts futurepress/epub.js#1407, minus its `range === unde\n[…]\nr-node re-anchoring, making a section's trailing\nlocation span back from the previous node as a cross-node range. Keeping\n`counter === 0` preserves correct anchoring; covered by a new regression\ntest.",
"is_bot": false,
"headline": "🐛 Ensure every section yields at least one location",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-27T10:52:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3acf035da5dad32cb3bace394477303108217d64",
"body": "* 🐛 Re-anchor CFI display after late content reflow\n\nA deep CFI restore could land an early page before the saved position:\nthe first moveTo() clamps the target to the last page of an under-measured\nlayout, and the section only grows afterwards as images, web fonts, or\nhost-injected theme CSS load. \n[…]\nthrowing assertion previously skipped the trailing vi.useRealTimers(),\nleaking fake timers into later tests. Hoisting the toggle into\nbeforeEach/afterEach guarantees restoration regardless of outcome.",
"is_bot": false,
"headline": "🐛 Re-anchor CFI display after late content reflow",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-27T10:26:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "97f8fc1ce776be0e8a656731ac5fcde87fcca713",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.5",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-22T11:43:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d1e93ddabb30f32cd222fb84e90e4d2c4b0d8dd3",
"body": "The word-splitting loop nulled `range` after pushing the first word, so\nthe next splitter could not close the second word — it was silently\ndropped. \"a b c d\" produced [\"a\",\"c\",\"d\"]; any two-word node produced\nonly its first word. findTextStartRange/findTextEndRange then had no\nrange covering the dr\n[…]\nite as a straight walk over every splitter, emitting one range per\nnon-empty word plus the trailing word. Using splitter.length instead of\nthe hardcoded +1 also fixes multi-character custom splitters.",
"is_bot": false,
"headline": "🐛 Stop dropping interior words in splitTextNodeIntoRanges",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-22T11:40:11Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "66f5bfb892df7de1e34f7916455a8c31878be5b3",
"body": "splitTextNodeIntoRanges computed split positions against the trimmed\ntext but applied those offsets to the raw node. Text nodes with leading\nwhitespace (pretty-printed XHTML, e.g. \"\\n Hello world\") shifted every\nrange left by the trimmed prefix, so the first range started inside the\nwhitespace and \n[…]\n the wrong characters — producing\nincorrect CFIs and page boundaries.\n\nAdd the leading-whitespace delta to each DOM offset. The set of ranges\nproduced is unchanged; only their positions are corrected.",
"is_bot": false,
"headline": "🐛 Align split ranges past leading whitespace in text mapping",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-22T11:17:06Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "df5232522d846e0d044b1b0ef2a05822e828face",
"body": "splitTextNodeIntoRanges split on spaces only, so spaceless scripts\n(CJK) collapsed to a single whole-node range. findTextStartRange could\nthen never locate a column boundary mid-paragraph and fell through to\noffset 0 — every page beginning inside a long Chinese paragraph\nreported the paragraph's sta\n[…]\n,\nbut text-indent gates it off for exactly these paragraphs.\n\nTeach the fallback to split spaceless CJK per code point (Latin runs\nkept whole, surrogate pairs intact), reusing the now-exported CJK_RE.",
"is_bot": false,
"headline": "🐛 Split spaceless CJK text so page-start CFI isn't offset 0",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-22T10:11:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ca1723af6d2ddf8ceef154ab7ebdc78d05581073",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.4",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-19T09:44:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7213b9440bf6057fd3a380f780d6766ccbf11f3f",
"body": "The canvas fast path estimates node positions from a uniform\ntext-width-to-scrollWidth ratio. For a reflowable section rendered in\na rendition shared with viewport-pinned fixed-layout siblings the\nmapping is non-linear, so findStart/findEnd could select wrong nodes\nand emit start after end in docume\n[…]\nrt/end ranges directly via\nRange.compareBoundaryPoints (cheap, no CFI string round-trip) and,\nwhen inverted, recomputes the page with the monotonic DOM walk via a\nnew noFast flag on findStart/findEnd.",
"is_bot": false,
"headline": "🐛 Guard against inverted page CFIs from canvas fast path",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-19T09:41:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "077c5bfc4d7c93379740aecbe6bcd09a364ed2ae",
"body": "jsdom's AbortSignal is a different realm than Node's undici fetch,\nwhich rejects it with a TypeError before it can abort — so request.ts's\nAbortError-passthrough branch was never exercised and the error fell\nthrough to the EpubError wrapper, failing the assertion.\n\nStub fetch to reject with the AbortError undici/browsers actually\nthrow, so the test verifies request()'s real contract deterministically\nwithout depending on cross-realm AbortSignal compatibility.",
"is_bot": false,
"headline": "✅ Fix AbortSignal test cross-realm mismatch under jsdom",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-05-19T09:41:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bdc70ddb15aa42ddd99dd5cbcb25da7f935454fd",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.3",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-14T09:48:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "15f00726d313fa3cd9f72eda75ae456acd73689c",
"body": "- request() with a pre-aborted signal surfaces AbortError verbatim\n (not wrapped as EpubError)\n- IframeView.destroy() mid-render rejects the render promise with\n AbortError and suppresses the loaderror/rendered events",
"is_bot": false,
"headline": "✅ Test AbortSignal propagation through request and IframeView",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-14T09:31:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4d59d69ec0700574268fcb27ab69b60f0869daee",
"body": "Five feature-detected enhancements, each with an ES2020-compatible\nfallback so the library still runs on the declared Chrome 80 / Safari\n13.4 / Firefox 74 floor (http:// intranet + file:// contexts).\n\n- defer delegates to Promise.withResolvers() when available\n- request() / Section.load/render / Ifr\n[…]\n .epub-view containers get contain: layout paint to isolate sibling\n reflows (size containment deliberately excluded — expand() still\n needs to read the iframe document's natural scrollWidth/Height)",
"is_bot": false,
"headline": "🚀 Phase 2 browser platform modernization",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-14T09:09:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "95fb415a14b193835b7b633470cf145f8e1f0e4c",
"body": "Unwinds ES2022 runtime API usage that excluded http:// intranet and\nfile:// deployments plus Firefox 74-94. The declared baseline moves\nfrom Chrome 93 / Safari 15.4 / Firefox 95 to Chrome 80 / Edge 80 /\nFirefox 74 / Safari 13.4 (Q1 2020).\n\n- Restore Math.random v4 fallback in uuid() behind a module-\n[…]\nruction .cause assignment,\n success criteria updated.\n- README browser compatibility row: ES2020 floor with explicit\n http:// / file:// support note.\n\n1010/1010 tests pass; typecheck and lint clean.",
"is_bot": false,
"headline": "♻️ Drop browser floor to ES2020 for http:// intranet support",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-14T04:05:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1ac277a4c825212381396835c0ca565eb349fa2c",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.2",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-10T15:58:41Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2cfc258009d2829c4bae51c23aea666112523f49",
"body": "Adds 8 unit tests for ContinuousViewManager.erase() covering the\nscroll-compensation arithmetic on each code path: vertical axis,\nhorizontal LTR, horizontal RTL (non-fullsize and fullsize variants),\nfractional bounds.width via Math.floor, the no-above branch that\nshouldn't adjust scroll at all, and \n[…]\ning tasks cleanly.\n\nThe four next()/prev() tests that trigger queue activity now\nexplicitly call manager.destroy() so the fix actually runs on\ntheir leaked managers; otherwise the flake would persist.",
"is_bot": false,
"headline": "✅ Test erase() scroll-compensation, stop task queue on destroy",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-10T15:50:19Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7272ff6bc3fb262fc7128ca930c0fdf0630085bc",
"body": "…ying them",
"is_bot": false,
"headline": "🐛 Prevent scroll flicker by hiding off-screen views instead of destro…",
"author_name": "EternalHeart",
"author_login": "wh131462",
"committed_at": "2026-04-10T14:57:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6ccd95ebdb6dc3f683da39b99d468ee92c445a7f",
"body": "Locks in the `if (e.persisted) return` guard added in a30e517 so a\nfuture refactor can't silently strip it and break bfcache restore.\nEach manager gets two tests: persisted=false calls destroy (and for\nthe continuous manager, also flips `ignore`), persisted=true leaves\nthe manager alive. Tests invok\n[…]\npto.randomUUID,\nArray.prototype.at, Object.hasOwn, and Error cause. The Phase 0.5\nsection's own bullet list said Firefox 95 for crypto.randomUUID, so\nthe \"Firefox 78\" line was internally inconsistent.",
"is_bot": false,
"headline": "✅ Test pagehide bfcache guard, fix plan browser floor",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-09T15:38:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7a79913b24c9c9f22589184fe41213fe5975dd80",
"body": "Delete dead IE/pre-ES2016 fallback branches and convert indexOf\ncomparisons to intent-specific string/array methods. All items are\nbehavior-preserving; 993/993 tests pass.\n\nDead code removal:\n- uuid(): drop Math.random/Date.getTime fallback — crypto.randomUUID\n is universal on the library's Chrome \n[…]\nepubcfi(\" comment\n (startsWith/endsWith guard states the intent)\n\nAlso documents Phase 0.5 in plan/modernization.md and marks Phase\n1.2/1.3 as already-landed (superseded by 0.5.1 in the case of 1.2).",
"is_bot": false,
"headline": "🚀 Phase 0.5 pre-2016 legacy cleanup",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-09T15:38:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9f7ca4ad9d04d9ab41be64eb0a8d849b64cd33ac",
"body": "Drop-in adoption of 2016–2022 browser APIs to reduce hot-path overhead,\nrestore bfcache, and remove hand-rolled polyfills — all with transparent\nfallbacks where needed.\n\n- Replace window \"unload\" with \"pagehide\" in both view managers and\n guard on event.persisted so bfcache entry leaves the manager\n[…]\nrnization.md documenting the full three-phase strategy,\n with the bfcache pitfall written into 1.1 so Phase 2+ don't inherit\n the same wrong assumption.\n\n993 tests passing, typecheck and lint clean.",
"is_bot": false,
"headline": "🚀 Phase 1 browser platform modernization",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-09T15:38:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f6c5f417281c2cf908257b12edad26143732c602",
"body": "Local-only bench comparing bundle size and runtime perf against\nepubjs 0.3.93. Puppeteer drives headless Chrome through a harness\npage that loads each library via UMD, runs cold-parse, first-display,\nnext-page, locations.generate, currentLocation, and heap scenarios,\nthen writes results to gitignore\n[…]\nroject Gutenberg, downloaded on first run. Headline number\non the large fixture: locations.generate(1000) drops from ~43s to\n~159ms.\n\nAdds Performance section to README with numbers for both fixtures.",
"is_bot": false,
"headline": "🚀 Add benchmark suite comparing epub-ts to epubjs",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-08T17:23:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1c06f025132a4d3f26e64d66ae7d95ba031087f8",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.1",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-08T14:50:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3e179bbddfcea7503905403f2f0ffcd9f301556e",
"body": "Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.4.1 to 6.4.2.\n- [Release notes](https://github.com/vitejs/vite/releases)\n- [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md)\n- [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite)\n\n---\nupdated-dependencies:\n- dependency-name: vite\n dependency-version: 6.4.2\n dependency-type: direct:development\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "Bump vite from 6.4.1 to 6.4.2",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-08T14:34:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c927eeb2c7d8ee775381c943cf2e31f2b4bc3254",
"body": "Bumps [picomatch](https://github.com/micromatch/picomatch) from 4.0.3 to 4.0.4.\n- [Release notes](https://github.com/micromatch/picomatch/releases)\n- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)\n- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)\n\n---\nupdated-dependencies:\n- dependency-name: picomatch\n dependency-version: 4.0.4\n dependency-type: indirect\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "Bump picomatch from 4.0.3 to 4.0.4",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-08T14:34:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "49e09b896c4bdc838392c2988ffe2721d770a7c3",
"body": "Bumps [undici](https://github.com/nodejs/undici) from 7.20.0 to 7.24.1.\n- [Release notes](https://github.com/nodejs/undici/releases)\n- [Commits](https://github.com/nodejs/undici/compare/v7.20.0...v7.24.1)\n\n---\nupdated-dependencies:\n- dependency-name: undici\n dependency-version: 7.24.1\n dependency-type: indirect\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "Bump undici from 7.20.0 to 7.24.1",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-08T14:34:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8d237870279f1117ddf17e698a70a003ff446cb8",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.57.1 to 4.59.0.\n- [Release notes](https://github.com/rollup/rollup/releases)\n- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)\n- [Commits](https://github.com/rollup/rollup/compare/v4.57.1...v4.59.0)\n\n---\nupdated-dependencies:\n- dependency-name: rollup\n dependency-version: 4.59.0\n dependency-type: indirect\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "Bump rollup from 4.57.1 to 4.59.0",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-08T14:34:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5a708e194db6c3504d188e66a650d06c829f8d2d",
"body": "* 🚀 Add node-level canvas binary search to Mapping.findStart/findEnd\n\nExtends the canvas-based optimization from c7f65ed to the node-finding\nwalk in findStart()/findEnd(). Previously, every text node in a chapter\ntriggered a nodeBounds() reflow (O(N) per call). Now, cumulative\ndocument-level text wi\n[…]\nalso the leftmost match in document order).\n\n- Clarify cumDocWidth doc on PreparedNode: it's cumulative width from\n the start of the prepared root, not \"document start\" (the root may\n be a subtree).",
"is_bot": false,
"headline": "🚀 Add node-level canvas binary search to Mapping.findStart/findEnd (#12)",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-08T14:28:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3ac2c1558b919a24c95c69a52261f2a9b8f56dd2",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.6.0",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-01T17:22:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8d35cac7190aa580a1eb5a38c8fbd0164ebc2078",
"body": "- Add publish.yml triggered on GitHub releases, reusing CI workflow\n- Enable workflow_call on ci.yml so publish can invoke it\n- Add publishConfig with public access and provenance in package.json",
"is_bot": false,
"headline": "🔧 Add npm Trusted Publishing workflow with OIDC provenance",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-01T17:22:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "318030c433df7d52da0f2a8b3e2b365f87aa3a54",
"body": "Lead with what developers get (drop-in replacement, typed, 1 dep,\nmaintained) instead of implementation details. Restructure README\nto put migration and quick start first. Update package.json\ndescription. Refresh PROJECT_STATUS with at-a-glance summary and\ncurrent test counts (976 tests, 40 files).",
"is_bot": false,
"headline": "📝 Rewrite docs with value-focused messaging for developers",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-01T17:02:54Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c7f65edfd91873c79dfc3451581433b9739afbe5",
"body": "…(#9)\n\n* 🚀 Add canvas-based text measurement and dirty-flag dimension caching\n\nLayer 1 — Dirty-flag caching in IframeView.expand():\n- Cache textWidth/textHeight results, only re-measure when content\n changes (RESIZE/EXPAND events or setLayout calls)\n- Pre-populate cache from resizeCheck() to avoid \n[…]\nndTextEndRange uses column start\n- Track prevElPos in findEnd so $prev gets its own DOMRect, not the\n overshot node's bounds\n- Remove dead this.horizontal ternaries inside horizontal-guarded branches",
"is_bot": false,
"headline": "🚀 Add canvas-based text measurement and dirty-flag dimension caching …",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-04-01T16:54:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "2108ded853ca6e1fbf692ea97691c4bbfeadf533",
"body": null,
"is_bot": false,
"headline": "✨ Allow custom underline styles",
"author_name": "Ng Wing Tat, David",
"author_login": "nwingt",
"committed_at": "2026-04-01T03:19:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b87d2b84d8984c79fd5f64f2f97b6b863d745f54",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.5.1",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-30T08:29:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5497bb772ec70bd94607fe8bf1772de7c02ef4e0",
"body": "Triaged 16 TODOs: removed 8 dead/obsolete comments, reworded 5 as\nknown limitations, kept 3 actionable validation gaps in epubcfi.ts\nand pagelist.ts.",
"is_bot": false,
"headline": "🔥 Remove 13 stale TODO comments inherited from epubjs",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-27T20:04:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c7dc58924a0663cf09ef63228a763b7ee774a681",
"body": "- New test/inline-view.test.ts covering all public methods\n- Extract createMockSection/createMockLayout to test/view-mocks.ts\n shared by both iframe and inline-view tests\n- Use EVENTS constants instead of string literals for event names",
"is_bot": false,
"headline": "✅ Add 69 InlineView tests, extract shared view mocks",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-27T19:57:33Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6d76f48bf96a99cfc2adaac0e4373f3de17de81c",
"body": "…ate docs\n\n- Export Container from index.ts (was only in node.ts)\n- Remove @ts-expect-error in ContinuousViewManager append/prepend by\n returning Promise.resolve(view) to match base class signature\n- Regenerate API docs (adds Container, EpubError, event type interfaces)\n- Update PROJECT_STATUS.md with remaining work items",
"is_bot": false,
"headline": "♻️ Export Container, fix ContinuousViewManager type mismatch, regener…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-27T19:44:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0e39440a760299c5571b4c6bd7f966854cc3073b",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.5.0",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-27T19:23:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e2253602e8ae26cbaa6a852159b29e80322bfaa7",
"body": "- Extract duplicate handleResponse from Archive and Store into shared\n utility in utils/core.ts\n- Add EpubError class replacing plain {message, stack, status} objects\n at 6 throw sites; export from index.ts\n- Convert ~55 == / != to === / !== across 22 files, keeping != null\n where intentional; simplify boolean comparisons in Queue",
"is_bot": false,
"headline": "♻️ Extract handleResponse, add EpubError, enforce strict equality",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-04T00:19:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dbc7473aeaca9049e8e56c623fbf82b79bc2510d",
"body": "Matches old XHR behavior where error message contained the\nresponse body, falling back to statusText if empty.",
"is_bot": false,
"headline": "🐛 Include response body in fetch error message",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T18:16:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d727e1e259f2c81a6bfb1529f86cd2aeb0d57c78",
"body": "- snap.ts: use easing return value for scroll interpolation (was linear)\n- request.ts: only re-wrap Blob when cross-realm (skip copy in browsers)\n- archive.ts: hoist _URL to module scope (was repeated 3 times)\n- contents.ts: rename COLUMN_AXIS to WEBKIT_COLUMN_AXIS (non-standard)",
"is_bot": false,
"headline": "🐛 Fix review findings from modernization commits",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2e481538ef955be972ba871183f2ab0920c76d3b",
"body": "Change non-browser fallback from `false` to a microtask-based\nscheduler. Remove boolean union from Queue.tick type. Remove\nunused tick property from ContinuousViewManager.",
"is_bot": false,
"headline": "♻️ Simplify requestAnimationFrame export",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "33b8f8130878d2cab3df9f60eba7ae24bcb155c4",
"body": "Convert store, archive, section, locations, snap, and continuous\nmanager from defer pattern to async/await. Simplify defer class\nby removing id property and Object.freeze.",
"is_bot": false,
"headline": "♻️ Replace trivial defer usage with async/await",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "fb7f4d2b00aef9304f0c2ccb3f8a170b74a2f9d8",
"body": "Rewrite request.ts to use async fetch(). Remove NodeXHR polyfill from\ntest setup (Node 18+ has native fetch). Add ResizeObserver polyfill for\njsdom. Add missing toc.ncx test fixture (previously masked by XHR bug\nthat accepted 404 responses with HTML bodies as valid XML).",
"is_bot": false,
"headline": "♻️ Replace XMLHttpRequest with fetch",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6a2093abf283663f269fa83afce29d33a021b97f",
"body": "These are intentional macrotask deferrals to let CSS apply. The 1 vs 0\nmakes no functional difference (browsers clamp to ~4ms), but 0 signals\nintent more clearly.",
"is_bot": false,
"headline": "♻️ Normalize setTimeout(fn, 1) to setTimeout(fn, 0)",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e6f42ef85d0045aebe35e800e6e5d4d1424cdf0f",
"body": "ResizeObserver is universally supported since 2020. Remove the\nsetTimeout-based polling fallback (resizeListeners), visibility\nchange handler, and related property declarations.",
"is_bot": false,
"headline": "🔥 Remove ResizeObserver fallback path",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c7e26e7b821dddc3f2157bbbdc1a3c738bc528ad",
"body": "Remove 500ms setTimeout delay per orientation change that worked\naround a 2017 iOS measurement bug (ampproject/amphtml#8479).\nResize and emit synchronously. Remove square-window guard in resize().",
"is_bot": false,
"headline": "🔥 Remove iOS 10.3 orientation workaround",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a1d78c120fd33918d33df3b2c8ad2a4fbc8fd918",
"body": "All standard CSS column/writing-mode properties are unprefixed since\n2015+. Hardcode -webkit-column-axis (non-standard, WebKit-only).\nSimplify writingMode() to use direct property access.",
"is_bot": false,
"headline": "🔥 Remove prefixed() CSS helper",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3feac942d20e7c3b8aefde738a5db1861b7e2ee7",
"body": "webkitURL (Safari 6, 2012) and mozURL (Firefox 4, 2011) are long dead.\nRemove fallback chains and Window interface augmentation.",
"is_bot": false,
"headline": "🔥 Remove webkitURL/mozURL fallbacks",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5adef81b75a20a60148c7e42e823155758fdc318",
"body": "Remove unused isChrome variable (inline negation into isWebkit),\n-webkit-overflow-scrolling: touch (native since iOS 13), and\nDate.getTime fallback in now() (performance.now universal since 2015).",
"is_bot": false,
"headline": "🔥 Remove dead code and obsolete polyfills",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T17:43:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0ebc6fee4c1c2e58693449f722d93d92a4b0aaae",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.4.9",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:44:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1d11f2c1192a983a05babe1a85297b228de70148",
"body": "book.opened no longer waits for all resource replacements (blob URL\ncreation) to complete. Instead, the replacement promise is stored on\nbook.replacementsReady, and the serialize hook awaits it only when a\nsection is actually rendered. This reduces time-to-first-render by\nthe full resource loading time for image-heavy EPUBs.\n\nAlso fixes a latent race where book.opened could resolve before\nnavigation was loaded (previously masked by slow replacements).",
"is_bot": false,
"headline": "⚡ Defer blob URL creation to not block book.opened",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:44:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b38a5f86d4cbe49e5883f98ec32102cad3b98056",
"body": "When Queue.tick is false, run() now schedules via Promise.resolve()\ninstead of requestAnimationFrame, avoiding ~16ms rAF overhead per\ndequeue. Locations sets tick=false since location generation is\nbackground computation, not rendering. Saves ~1.6s for 100 chapters.",
"is_bot": false,
"headline": "⚡ Add microtask mode to Queue, use it in Locations",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:08:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "89ecf9a55da5b525a41a5c7ff6b201ccb980631b",
"body": "Add cssBatch() method that applies multiple CSS properties in a single\npass without intermediate getComputedStyle reads. Refactor columns(),\nsize(), and fit() to use it, eliminating ~15 forced layouts per call.",
"is_bot": false,
"headline": "⚡ Batch CSS writes in Contents.columns/size/fit",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:08:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dea13c9eba6f1c06d81a16a756fbf2d9b9ef6702",
"body": "Replace the O(N×M) loop that created N intermediate string copies and\nN regex compilations with a single combined regex and one .replace()\ncall. Alternatives are sorted longest-first to prevent substring\ncollisions (e.g. \"img.jpg\" vs \"big-img.jpg\").",
"is_bot": false,
"headline": "⚡ Single-pass regex for CSS URL substitution",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:08:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "39cb57e15702b0c3e3491f23020e4ea09e0ee992",
"body": "Eliminates the async Blob→FileReader roundtrip with a synchronous\nTextDecoder.decode() call, removing unnecessary async overhead.",
"is_bot": false,
"headline": "⚡ Replace FileReader with TextDecoder in Store.getText()",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:06:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f8106447320e2631d35c1bf5a96b8956135a8dc8",
"body": "The artificial 100ms setTimeout between each section during locations\ngeneration added 10+ seconds of pure waiting for 100-chapter books.\nThe Queue's rAF scheduling already yields to the browser — the extra\ndelay on top was unnecessary. Consumers can still pass a custom pause.",
"is_bot": false,
"headline": "⚡ Reduce Locations default pause from 100ms to 0",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T15:06:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8a5d62b302dcc96a835b2473ee0ba3070654a0ec",
"body": "The condition in Book.unpack() short-circuited on this.archived,\nignoring the replacements setting. This caused unnecessary blob URL\ncreation and CSS processing even when replacements was set to \"none\".\nAlso add early return in Resources.replaceCss() for the \"none\" case.",
"is_bot": false,
"headline": "🐛 Respect replacements: \"none\" for archived EPUBs",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T08:56:16Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "79556d70c880f87e21415cea15798150a05e30eb",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.4.8",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T07:39:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "03c601c0b54b812e148d945d4a22bc970d8f299e",
"body": "- Call section.unload() in IframeView.destroy() to release off-screen\n chapter DOMs\n- Revoke blob URLs in Resources.destroy() before clearing replacementUrls\n- Fix Store.destroy() to revoke urlCache values instead of keys",
"is_bot": false,
"headline": "🐛 Plug memory leaks in view, resource, and store cleanup",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-03-03T06:51:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "270dfda066290d08d06a8c33ee218020eccd488e",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.4.7",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-26T15:52:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "df2e349e43fec9263a680a7e975793c4de26b11b",
"body": "Re-expand view before jumping to next/prev section in next()/prev() to\nforce layout reflow with current dimensions. On Android Chrome/Brave,\nthe initial expand() measures before CSS column layout fully settles,\nmaking scrollWidth one page too narrow and the last page unreachable.\n\nApplied the re-exp\n[…]\nl LTR, horizontal RTL (both scroll types), and vertical.\n\nAlso re-enable fontLoadListeners() (commented out since 2016) so font\nload events trigger resizeCheck(), proactively correcting iframe sizing.",
"is_bot": false,
"headline": "🐛 Fix Android Chrome/Brave skipping last page of each EPUB chapter",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-26T15:37:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "35f74a1c4b908c6eea91faa53c460c792d91c1c4",
"body": "Add 20 new Contents tests (textWidth/Height, root, expand, resizeCheck,\nlocationOf, size, columns, scaler, epubReadingSystem, listenedEvents)\nand 8 new Mapping tests (walk, splitTextNodeIntoRanges edge cases, page\nwith mocked body, section empty ranges). Update test status table from\n28 files/481 tests to 38 files/876 tests.\n\nAlso fix test cleanup: destroy Contents instances in afterEach to stop\nasync polling timers, and add afterEach with vi.restoreAllMocks() to\nmapping tests.",
"is_bot": false,
"headline": "✅ Add test coverage for Contents and Mapping, update PROJECT_STATUS.md",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-25T16:07:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "39ff9f957ee0404d7f0cefea4600be7c415e0ba0",
"body": "Expand test coverage for three under-tested modules:\n- locations.test.ts: 1 → 28 tests (constructor, load/save, cfi methods, setCurrent, countWords, destroy)\n- section.test.ts: 4 → 19 tests (constructor, load/render/unload, reconcileLayoutSettings, cfi methods, destroy)\n- resources.test.ts: 11 → 21 tests (createUrl modes, replacements modes, error handling, createCssFile, replaceCss)",
"is_bot": false,
"headline": "✅ Add unit test coverage for Locations, Section, and Resources",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-21T20:27:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3e62ce53bb4d4d603d5443adb6e85bcb6fdd0db7",
"body": "Add 303 new tests (481 → 784) covering Stage, marks-pane, Contents,\nMapping, IframeView, Rendition, DefaultViewManager, ContinuousViewManager,\nSnap, and Store. Add fake-indexeddb dev dependency for Store tests.",
"is_bot": false,
"headline": "✅ Add rendering-layer test coverage for 10 modules",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-21T19:39:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cb9acf69106d842621eed38b8329e980854ec4e1",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.4.6",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-20T04:30:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8dca511c6cd0defc71f41871a523ffb9dddfa3ac",
"body": "Replaces the generic string-keyed IEventEmitter with IEventEmitter<E extends EventMap>,\ngiving type-safe on/off/emit calls. Defines per-class event map interfaces (BookEvents,\nRenditionEvents, ContentsEvents, etc.) and exports them from the public API.",
"is_bot": false,
"headline": "🏷️ Add typed event emitter generics across all emitter classes",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-20T04:21:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "56a75ca64d667a56a3b5e4e27b36a234411d5ad9",
"body": "…URIComponent in Store\n\n- resources.ts: revert fields from `Foo | undefined` back to definite assignment (`Foo!`),\n removing scattered `!` assertions throughout all method bodies; update destroy() to use `undefined!`\n- store.ts: replace `window.encodeURIComponent` with global `encodeURIComponent` in add() and put(),\n matching the same fix already applied to archive.ts (fixes Node.js compatibility)",
"is_bot": false,
"headline": "🐛 fix: restore definite assignment in Resources and fix window.encode…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T16:31:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8a46fc43493077bb1d66af92a92b13614f0169e3",
"body": "…s eslint cleanup (2016)",
"is_bot": false,
"headline": "🐛 Restore Spine.prepend() unshift accidentally commented out in epubj…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T06:08:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3476264533df8fff1c06e6cf220a634d30a0540d",
"body": "- Fix CLAUDE.md title (AGENTS.md → Agent Instructions)\n- Replace hardcoded status in CLAUDE.md with reference to PROJECT_STATUS.md\n- Rewrite copilot-instructions.md (235 → 51 lines): remove duplicated\n conventions, fix stale data (wrong dep count, deleted types/ dir,\n outdated test count, wrong any count), reference CLAUDE.md and\n PROJECT_STATUS.md as single sources of truth",
"is_bot": false,
"headline": "📝 Deduplicate CLAUDE.md and copilot-instructions.md",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T05:40:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "49bc96420299d1b4c15e06dfb47b6443cdfdfe52",
"body": "…ructions.md",
"is_bot": false,
"headline": "📝 Update test counts to 481 across PROJECT_STATUS.md and copilot-inst…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T05:40:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "06e5e78e272513d6fe5fdc27773f425a194b5379",
"body": "Fix vacuous assertions in url.test.ts and resources.test.ts identified\nduring code review. Strengthen resources.test.ts with get() found case,\nsubstitute with url arg, and replacements mode \"none\". Add trivial\nnotable gap tests: PageList.pageFromCfi/percentageFromCfi with populated\ndata, Archive.openUrl and createUrl blob branch, Queue.Task class.",
"is_bot": false,
"headline": "✅ Fix vacuous assertions and strengthen test coverage (471 → 481 tests)",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T05:34:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "747d1105e57d04b8a0ef12d8db5d70650d0c5641",
"body": "…st, and Themes\n\n- replacements.test.ts (23): replaceBase, replaceCanonical, replaceMeta, replaceLinks\n (security: javascript:/data:text/html stripping), substitute with regex/percent-encoding\n- views.test.ts (27): append, prepend, insert, remove, first/last, indexOf, get, slice,\n find by section,\n[…]\ny, blob, 404 rejection\n- themes.test.ts (21): register dispatch, registerUrl/Rules/Css, select, add, override,\n removeOverride, fontSize, font, destroy\n\nTotal: 394 → 471 tests passing across 28 files",
"is_bot": false,
"headline": "✅ Add 77 tests across 4 new test files for Replacements, Views, Reque…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T05:17:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0481d369b45c7d47a4869218d598c7494615174c",
"body": "- path-utils.test.ts (29): resolve, relative, dirname, isAbsolute, parse\n- mime.test.ts (24): EPUB types, images, fonts, audio, video, case-insensitive\n- queue.test.ts (22): enqueue, dequeue, flush, dump, clear, pause, stop\n- hook.test.ts (18): register, deregister, trigger (sync/async/error), clear\n[…]\n.test.ts (17): absolute/relative/base parsing, resolve, relative, toString\n- event-emitter.test.ts (14): mixin, on/off/emit, chaining, listener snapshot\n\nTotal: 253 → 394 tests passing across 24 files",
"is_bot": false,
"headline": "✅ Add 141 tests across 7 new test files for utility layer",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-19T05:03:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0a73b5a3e0437687b45d8108f6093158d387f0c2",
"body": "…es, and Annotations\n\n- archive.test.ts (22): getText, getBlob, getBase64, request, handleResponse, createUrl, destroy — with epubcheck ground-truth assertions against alice.epub\n- pagelist.test.ts (17): Nav HTML/NCX parsing, CFI href splitting, process, pageFromCfi, cfiFromPage, percentages, destro\n[…]\ntUrls, relativeTo, get, substitute, destroy\n- annotations.test.ts (13): constructor hooks, add/remove, highlight/underline/mark wrappers, each, Annotation class, update\n\nTotal: 191 → 253 tests passing",
"is_bot": false,
"headline": "✅ Add 62 tests across 4 new test files for Archive, PageList, Resourc…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-18T17:43:35Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f4bf9282779c44d1c7813a073b901b57a351e1fd",
"body": null,
"is_bot": false,
"headline": "✨ Add Node.js parsing-only entry point",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-18T10:50:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2b7f31c589cea65d489e38714519aa738c70a1c5",
"body": "…ntainer, Layout, DisplayOptions, and Spine\n\nExpected values verified against epubcheck v5.3.0. Also enhances book.test.ts\nwith sub-object parity assertions (metadata, TOC, spine). Total: 181 tests\npassing across 12 test files (up from 61 in 6).",
"is_bot": false,
"headline": "✅ Add 120 tests across 6 new test files for Packaging, Navigation, Co…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-17T19:37:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1d50f5f8bf2617e52d1fabacc5a64db36ecafcaf",
"body": "…error in hook.ts\n\nWhen replacements() rejected, the opening promise never resolved, causing\nbook.opened to hang forever. The catch block now resolves it since the book\ncontent is fully loaded regardless. Also upgrade console.log to console.error\nin hook.ts for consistency.",
"is_bot": false,
"headline": "🐛 Fix catch blocks: resolve hanging book.opened promise, use console.…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-17T18:24:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8860bd0ca565ab4116e46e9419bbba855ecaeb51",
"body": "…n.ts and book.ts\n\nConvert `manager` in rendition.ts and 5 properties (packaging, url,\nnavigation, pageList, resources) in book.ts from `| undefined` to\ndefinite assignment (`!:`), restoring the original epubjs API contract.",
"is_bot": false,
"headline": "🏷️ Reduce ~22 non-null assertions via definite assignment in renditio…",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-17T17:45:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9539a489228d514e5947bccdd26224bfb214635b",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.4.5",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-16T21:33:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "656ac75db1d742682030cfe83bf7c795347ed936",
"body": null,
"is_bot": false,
"headline": "🐛 Guard window reference in store.ts for Node.js compatibility",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-16T21:32:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "833440937a5ecce4e6a2d894c137a3b725564b59",
"body": "Convert 24 constructor-initialized properties from `T | undefined` to\ndefinite assignment (`!:`) in spine.ts, packaging.ts, book.ts, and\nrendition.ts. This removes `!` at access sites since the types are no\nlonger optional, and uses `= undefined!` in destroy() methods to allow\nteardown. Purely type-level refactor with no behavioral changes.",
"is_bot": false,
"headline": "🏷️ Reduce ~95 non-null assertions via definite assignment across 4 files",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-16T20:54:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9877eadab8ec5e622f1f14baf01aaeab956b9fd5",
"body": "Replace bare `Function` (which accepts any args and returns `any`) with\nspecific callable/constructor types: HookCallback, ViewManagerConstructor,\nViewConstructor, EventListener, and typed function signatures.",
"is_bot": false,
"headline": "🏷️ Replace ~33 Function types with proper signatures across 6 files",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-16T19:46:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "55fef0032886a8217db0841147ccefa265d223dd",
"body": "- Add return type annotations to manager methods (display, add, append, prepend, update, check, trim, mark)\n- Replace {} as any with proper type assertions in spine.ts and section.ts\n- Type event handler properties in snap.ts and default/index.ts with specific signatures\n- Fix variable types in loca\n[…]\ness property assignment with setAttribute\n- Type snap.ts settings without index signature, narrow constructor options\n- Remaining ~21 any types are in event emitter pattern (14) and annotations.ts (7)",
"is_bot": false,
"headline": "🏷️ Replace ~61 any types with proper types across 21 files",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-16T19:05:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c4cdfd4964777d4df90830539bf90bdba4abd257",
"body": null,
"is_bot": false,
"headline": "🔖 Bump version to 0.4.4",
"author_name": "William Chong",
"author_login": "williamchong",
"committed_at": "2026-02-16T06:49:04Z",
"body_truncated": false,
"is_coding_agent": false
}
],
"releases_count": 27,
"commits_last_year": 166,
"latest_release_at": "2026-07-17T23:23:47Z",
"latest_release_tag": "v0.6.9",
"releases_from_tags": false,
"days_since_last_push": 5,
"active_weeks_last_year": 12,
"days_since_latest_release": 5,
"mean_days_between_releases": 11.9
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": false,
"health_percentage": 37,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "@likecoin/epub-ts",
"exists": true,
"license": "BSD-2-Clause",
"keywords": [
"epub",
"ebook",
"reader",
"typescript",
"epubjs",
"parser",
"renderer"
],
"ecosystem": "npm",
"matches_repo": true,
"registry_url": "https://www.npmjs.com/package/@likecoin/epub-ts",
"is_deprecated": false,
"latest_version": "0.6.9",
"repository_url": "https://github.com/likecoin/epub.ts",
"versions_count": 27,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": 3,
"monthly_downloads": 3188,
"first_published_at": "2026-02-06T18:34:52.275000Z",
"latest_published_at": "2026-07-17T23:25:10.058000Z",
"latest_version_yanked": null,
"days_since_latest_publish": 5
}
]
},
"popularity": {
"forks": 6,
"stars": 14,
"watchers": 0,
"fork_history": {
"days": [
{
"date": "2026-02-15",
"count": 1
},
{
"date": "2026-02-17",
"count": 1
},
{
"date": "2026-03-30",
"count": 1
},
{
"date": "2026-04-09",
"count": 1
},
{
"date": "2026-04-10",
"count": 1
},
{
"date": "2026-07-12",
"count": 1
}
],
"complete": true,
"collected": 6,
"total_forks": 6
},
"star_history": null,
"open_issues_and_prs": 5
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [
"examples"
],
"has_llms_txt": false,
"has_dockerfile": false,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [
"tsconfig.json"
],
"toolchain_manifests": [],
"largest_source_bytes": 37482,
"source_files_sampled": 103,
"oversized_source_files": 0,
"agent_instruction_files": [
".github/copilot-instructions.md",
"AGENTS.md",
"CLAUDE.md"
],
"agent_instruction_max_bytes": 2061
},
"dependencies": {
"manifests": [
"package.json"
],
"advisories": {
"error": null,
"scope": null,
"source": null,
"findings": [],
"collected": false,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 0,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"npm"
],
"dependencies": [
{
"name": "jszip",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^3.10.1"
}
],
"all_dependencies": {
"error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"source": null,
"packages": [],
"collected": false,
"truncated": false,
"total_count": null,
"direct_count": null,
"indirect_count": null
}
},
"maintainership": {
"issues": {
"open_prs": 4,
"merged_prs": 20,
"open_issues": 1,
"closed_ratio": 0.5,
"closed_issues": 1,
"closed_unmerged_prs": 4
},
"bus_factor": 1,
"bot_contributors": 2,
"top_contributors": [
{
"type": "User",
"login": "williamchong",
"commits": 159,
"avatar_url": "https://avatars.githubusercontent.com/u/6198816?v=4"
},
{
"type": "User",
"login": "wh131462",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/48346853?v=4"
},
{
"type": "User",
"login": "nwingt",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/17264716?v=4"
}
],
"contributors_sampled": 3,
"top_contributor_share": 0.988
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml",
"docs.yml",
"publish.yml"
],
"has_docs_dir": true,
"linter_configs": [
"eslint.config.js"
],
"has_editorconfig": false,
"has_linter_config": true,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"package-lock.json"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 3,
"reason": "branch protection is not maximal on development and all release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 10,
"reason": "7 out of 7 merged PRs checked by a CI test -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 2/23 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 3,
"reason": "project has 1 contributing companies or organizations -- score normalized to 3",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 10,
"reason": "update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 9,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 10,
"reason": "26 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": 10,
"reason": "packaging workflow detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 2,
"reason": "dependency not pinned by hash detected -- score normalized to 2",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "SAST tool is not run on all commits -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "security policy file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": null,
"reason": "no releases found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 0,
"reason": "detected GitHub workflow tokens with excessive permissions",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 0,
"reason": "47 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "d2eaa1be3bc004ab5405e68ff748ffac70f2782b",
"ran_at": "2026-07-23T09:10:03Z",
"aggregate_score": 4.7,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": false
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-17T23:25:13Z",
"oldest_open_prs": [
{
"number": 16,
"created_at": "2026-04-23T02:57:09Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 21,
"created_at": "2026-06-14T10:26:02Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 22,
"created_at": "2026-06-20T09:34:42Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 23,
"created_at": "2026-06-30T02:38:01Z",
"last_comment_at": null,
"last_comment_author": null
}
],
"last_merged_pr_at": "2026-07-17T23:20:50Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": [
{
"number": 29,
"created_at": "2026-07-09T07:13:11Z",
"last_comment_at": null,
"last_comment_author": null
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/likecoin/epub.ts",
"host": "github.com",
"name": "epub.ts",
"owner": "likecoin"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 64,
"inputs": {
"security": 47,
"vitality": 83,
"community": 40,
"governance": 56,
"engineering": 86
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "good",
"name": "Vitality",
"value": 83,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 72,
"inputs": {
"commits_last_year": 166,
"human_commit_share": 0.96,
"days_since_last_push": 5,
"active_weeks_last_year": 12
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 5 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "push_recency",
"params": {
"days": 5
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "12/52 weeks with commits",
"points": 8.3,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 12
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "166 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 166
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "26 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 100,
"inputs": {
"releases_count": 27,
"latest_release_tag": "v0.6.9",
"releases_from_tags": false,
"days_since_latest_release": 5,
"mean_days_between_releases": 11.9
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "27 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 27
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 5 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 5
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~11.9 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 11.9
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "unverified",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": "repository_too_young",
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": null,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "maintenance record not established from the collected data",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_unverified",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "at_risk",
"name": "Community & Adoption",
"value": 40,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 24,
"inputs": {
"forks": 6,
"stars": 14,
"watchers": 0,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "14 stars",
"points": 18.1,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 14
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "6 forks",
"points": 5.8,
"status": "partial",
"details": [
{
"code": "forks",
"params": {
"count": 6
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "0 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 0
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "at_risk",
"name": "Community health",
"note": null,
"notes": [],
"value": 44,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": false,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "license file present, not a recognized license",
"points": 16.9,
"status": "partial",
"details": [
{
"code": "license_custom",
"params": {}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
},
{
"key": "ecosystem_adoption",
"band": "moderate",
"name": "Ecosystem adoption (downloads)",
"note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"registry_dependents"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 58,
"inputs": {
"packages": [
"@likecoin/epub-ts"
],
"dependents": null,
"ecosystems": "npm",
"total_downloads": null,
"monthly_downloads": 3188
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "3,188 downloads/month across npm",
"points": 46.7,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 3188,
"ecosystems": "npm"
}
}
],
"max_points": 80
},
{
"key": "registry_dependents",
"name": "Registry dependents",
"detail": "not reported by this ecosystem",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_reported_by_this_ecosystem",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "moderate",
"name": "Sustainability & Governance",
"value": 56,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 16,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 3,
"top_contributor_share": 0.988
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 99% of commits",
"points": 0.3,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 99
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "3 contributors",
"points": 4.1,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 3
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 1 contributing companies or organizations -- score normalized to 3",
"points": 3,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "moderate",
"name": "Issue & PR responsiveness",
"note": null,
"notes": [],
"value": 55,
"inputs": {
"merged_prs": 20,
"open_issues": 1,
"closed_issues": 1,
"issue_closed_ratio": 0.5,
"closed_unmerged_prs": 4
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "50% of issues closed",
"points": 23.4,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 50
}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "20/24 decided PRs merged",
"points": 31.9,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 20,
"decided": 24
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 2/23 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": null,
"notes": [],
"value": 68,
"inputs": {
"followers": 70,
"owner_type": "Organization",
"is_verified": null,
"owner_login": "likecoin",
"public_repos": 166,
"account_age_days": 4585
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "organization-owned",
"points": 30,
"status": "met",
"details": [
{
"code": "owner_organization",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "70 followers of likecoin",
"points": 13.3,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 70,
"login": "likecoin"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "166 public repos, account ~12 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 166
}
},
{
"code": "account_age_years",
"params": {
"years": 12
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"@likecoin/epub-ts"
],
"ecosystems": "npm",
"any_deprecated": false,
"min_days_since_publish": 5
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on npm",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "npm"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 5 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 5
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "27 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 27
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "excellent",
"name": "Engineering Quality",
"value": 86,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "good",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 84,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": true,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "3 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 3
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": "eslint.config.js",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "eslint.config.js"
}
}
],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "7 out of 7 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "excellent",
"name": "Documentation",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"topics": [
"epub",
"epub-reader",
"epub3",
"epubjs",
"npm",
"typescript"
],
"has_wiki": false,
"homepage": "https://likecoin.github.io/epub.ts/",
"has_readme": true,
"has_docs_dir": true,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 25,
"status": "met",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": "https://likecoin.github.io/epub.ts/",
"points": 15,
"status": "met",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": "6 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 6
}
}
],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "at_risk",
"name": "Security",
"value": 47,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "at_risk",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 47,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 17,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 1,
"scorecard_aggregate": 4.7
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection is not maximal on development and all release branches",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "7 out of 7 merged PRs checked by a CI test -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 2/23 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 1 contributing companies or organizations -- score normalized to 3",
"points": 0.8,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "update tool detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "26 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 2",
"points": 1,
"status": "partial",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is not run on all commits -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "detected GitHub workflow tokens with excessive permissions",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "47 existing vulnerabilities detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"name": "High-Risk Jurisdiction Exposure",
"note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
"notes": [
{
"code": "jurisdiction_evidence_limits",
"params": {}
}
],
"value": 100,
"inputs": {
"meaning": "self-published location evidence; not nationality or citizenship",
"red_flag": false,
"exposures": [],
"policy_countries": [
"Russia",
"Iran",
"North Korea"
],
"review_only_matches": 0,
"assessed_self_published_locations": 5
},
"components": [
{
"key": "policy_exposure_multiplier",
"name": "Policy exposure multiplier",
"detail": "no confirmed policy-scope location match",
"points": 100,
"status": "met",
"details": [
{
"code": "jurisdiction_no_match",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "good",
"name": "AI Readiness",
"value": 72,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 0.75,
"agent_instruction_files": [
".github/copilot-instructions.md",
"AGENTS.md",
"CLAUDE.md"
],
"agent_instruction_max_bytes": 2061
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": ".github/copilot-instructions.md, AGENTS.md, CLAUDE.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".github/copilot-instructions.md, AGENTS.md, CLAUDE.md"
}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "72 of 96 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 72,
"sampled": 96
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "moderate",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 64,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"package-lock.json"
],
"has_dockerfile": false,
"typed_language": true,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": true,
"typecheck_configs": [
"tsconfig.json"
],
"agent_commit_share": 0,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0.04
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": "eslint.config.js",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "eslint.config.js"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "tsconfig.json",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "tsconfig.json"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "4 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 4,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 2",
"points": 2,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "TypeScript",
"largest_source_bytes": 37482,
"source_files_sampled": 103,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "TypeScript (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "TypeScript"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/103 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 103,
"oversized": 0
}
}
],
"max_points": 55
}
]
},
{
"key": "ai_interfaces",
"band": "at_risk",
"name": "Machine-readable interfaces",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"example_dirs": [
"examples"
],
"has_mcp_signal": false,
"api_schema_files": []
},
"components": [
{
"key": "api_schema_openapi_graphql_proto",
"name": "API schema (OpenAPI/GraphQL/proto)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 40
},
{
"key": "mcp_server",
"name": "MCP server",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "runnable_examples",
"name": "Runnable examples",
"detail": "examples",
"points": 40,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "examples"
}
}
],
"max_points": 40
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"deps.dev does not index npm:@likecoin/epub-ts@0.6.9; advisories assessed against the repository dependency graph instead"
],
"report_type": "repository",
"generated_at": "2026-07-23T09:10:18.069240Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/l/likecoin/epub.ts.svg",
"full_name": "likecoin/epub.ts",
"license_state": "custom",
"license_spdx": null
}