Raw JSON report machine-readable
{
"data": {
"repo": {
"topics": [
"audio",
"audio-player",
"audio-visualization",
"canvas",
"music-player",
"typescript",
"vanilla-javascript",
"waveform",
"web-audio-api",
"arraypress",
"audio-peaks",
"bpm-detection",
"media-session",
"podcast-player"
],
"is_fork": false,
"size_kb": 23392,
"has_wiki": true,
"homepage": "https://waveformplayer.com",
"languages": {
"CSS": 15224,
"JavaScript": 227301,
"TypeScript": 204
},
"pushed_at": "2026-07-21T18:44:46Z",
"created_at": "2025-09-14T12:06:32Z",
"owner_type": "User",
"updated_at": "2026-07-21T18:45:54Z",
"description": "Lightweight, customizable audio player with waveform visualization",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "main",
"license_spdx_raw": "MIT",
"primary_language": "JavaScript",
"significant_languages": [
"JavaScript"
]
},
"owner": {
"blog": "https://arraypress.com",
"name": "David Michael",
"type": "User",
"login": "arraypress",
"company": null,
"location": "Remote",
"followers": 25,
"avatar_url": "https://avatars.githubusercontent.com/u/22668877?v=4",
"created_at": "2016-10-06T20:55:16Z",
"is_verified": null,
"public_repos": 305,
"account_age_days": 3583
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v1.23.0",
"kind": "minor",
"published_at": "2026-07-21T17:56:32Z"
},
{
"tag": "v1.22.0",
"kind": "minor",
"published_at": "2026-07-17T09:31:58Z"
},
{
"tag": "v1.20.0",
"kind": "minor",
"published_at": "2026-07-04T18:16:16Z"
},
{
"tag": "v1.19.3",
"kind": "patch",
"published_at": "2026-07-01T19:44:11Z"
},
{
"tag": "v1.19.1",
"kind": "patch",
"published_at": "2026-07-01T16:37:19Z"
},
{
"tag": "v1.19.0",
"kind": "minor",
"published_at": "2026-07-01T16:17:53Z"
},
{
"tag": "v1.18.0",
"kind": "minor",
"published_at": "2026-07-01T10:18:14Z"
},
{
"tag": "v1.17.0",
"kind": "minor",
"published_at": "2026-07-01T07:29:38Z"
},
{
"tag": "v1.16.1",
"kind": "patch",
"published_at": "2026-06-30T14:22:43Z"
},
{
"tag": "v1.16.0",
"kind": "minor",
"published_at": "2026-06-30T12:08:58Z"
},
{
"tag": "v1.15.0",
"kind": "minor",
"published_at": "2026-06-29T19:42:10Z"
},
{
"tag": "v1.14.0",
"kind": "minor",
"published_at": "2026-06-28T15:49:34Z"
},
{
"tag": "v1.13.1",
"kind": "patch",
"published_at": "2026-06-28T14:45:52Z"
},
{
"tag": "v1.13.0",
"kind": "minor",
"published_at": "2026-06-28T14:04:59Z"
},
{
"tag": "v1.12.1",
"kind": "patch",
"published_at": "2026-06-28T13:34:57Z"
},
{
"tag": "v1.12.0",
"kind": "minor",
"published_at": "2026-06-28T13:14:59Z"
},
{
"tag": "v1.11.0",
"kind": "minor",
"published_at": "2026-06-28T12:38:07Z"
},
{
"tag": "v1.10.0",
"kind": "minor",
"published_at": "2026-06-28T12:16:21Z"
},
{
"tag": "v1.9.0",
"kind": "minor",
"published_at": "2026-06-28T10:48:24Z"
},
{
"tag": "v1.8.1",
"kind": "patch",
"published_at": "2026-06-27T19:01:03Z"
},
{
"tag": "v1.8.0",
"kind": "minor",
"published_at": "2026-06-27T10:26:06Z"
},
{
"tag": "v1.7.2",
"kind": "patch",
"published_at": "2026-06-27T09:12:42Z"
},
{
"tag": "v1.7.0",
"kind": "minor",
"published_at": "2026-05-23T14:56:49Z"
},
{
"tag": "v1.6.0",
"kind": "minor",
"published_at": "2026-05-12T21:40:05Z"
},
{
"tag": "v1.3.5",
"kind": "patch",
"published_at": "2026-03-17T13:59:32Z"
},
{
"tag": "v1.3.4",
"kind": "patch",
"published_at": "2026-03-17T12:45:11Z"
},
{
"tag": "v1.3.3",
"kind": "patch",
"published_at": "2026-03-16T18:52:34Z"
},
{
"tag": "v1.3.2",
"kind": "patch",
"published_at": "2026-03-16T18:25:17Z"
},
{
"tag": "v1.3.1",
"kind": "patch",
"published_at": "2026-03-16T17:19:03Z"
},
{
"tag": "v1.3.0",
"kind": "minor",
"published_at": "2026-03-16T15:37:48Z"
},
{
"tag": "v1.2.2",
"kind": "patch",
"published_at": "2026-02-28T07:33:53Z"
},
{
"tag": "v1.2.1",
"kind": "patch",
"published_at": "2026-02-12T08:18:38Z"
},
{
"tag": "v1.2.0",
"kind": "minor",
"published_at": "2025-10-19T14:06:26Z"
},
{
"tag": "v1.1.0",
"kind": "minor",
"published_at": "2025-09-14T22:39:00Z"
},
{
"tag": "v1.0.1",
"kind": "patch",
"published_at": "2025-09-14T17:09:10Z"
}
],
"recent_commits": [
{
"oid": "5aa8f4cf237805d1299be3564bdb2ebf18d0621a",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: bump CLAUDE.md size-figure note to 1.23.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-21T18:44:42Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "82d5c4dfde0f471f03ecfed42ef562acb319baa5",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "release: 1.23.0 (configurable crossOrigin, unset by default)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-21T17:56:32Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "4b199678228bc582014cb69ec4af4ea8862b6cd1",
"body": "\"~10KB JS gzip is the budget\" had sat below the actual size for several\nreleases — main was already 12,479 bytes before 1.22.0 — so it could\nnever flag a regression. Records the real figure (~12.4KB JS / ~1.8KB\nCSS as of 1.22.0) with a ~13KB ceiling, and points at the marketing\nsite's copy of the number so the two don't drift apart again.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: correct the stale size budget in CLAUDE.md",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-17T14:50:54Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "1eb01fc266f593fde986a5d5191e167cf1062fec",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "release: 1.22.0 (artwork placement, button radius, escaping)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-17T09:31:58Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "e358a4b5a9d4905af78d3b30db6c492b0bbfc412",
"body": "…Size\n\nReview feedback: cover mode still drew the transport button's ring around\nthe artwork, so the cover read as art stuffed inside a button rather than\nas the control. It also inherited defaults that are wrong for artwork in\nboth directions — 36px shrinks a cover to a smudge, and 50% rounding\ncro\n[…]\nng since buttonSize\nshipped; found because cover mode depends on that rule.\n\ncss.test.js guards both, since jsdom runs no cascade.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(css): let the cover be the control, and stop mobile eating button…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-17T09:20:52Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "1a6853ad467e10d2fbc455b83aa4f9b7935a27a5",
"body": "Adds `artworkPosition: 'info' | 'button'` (+ `data-artwork-position`),\nchoosing where the `artwork` image renders. 'info' is the default and is\nthe existing behaviour. 'button' fills the play/pause button instead, and\nstill works with the info row hidden. A placement rather than a boolean:\nonly one \n[…]\ne worst case from the declared vars and fails below 3:1.\n\nStructural like showInfo/buttonStyle — construction-time, not per-track.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: add artworkPosition for cover art on the play button",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-17T08:48:58Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "3a014629df1151c62c7bae9c86c2be1d667e1c7f",
"body": "Adds `buttonRadius` (+ `data-button-radius`), mirroring `buttonSize`: a\nnumber is treated as px, a string is used verbatim, and null leaves the\nstylesheet's circular 50% in place. Sets the `--wfp-btn-radius` var,\nwhich shapes the `circle` style's box — the bare `minimal` glyph has no\nbox to round.\n\n\n[…]\nk.\n\nExtracts `formatCssLength` and a `setLength` data-attribute helper so\nbuttonSize and buttonRadius share one parse/format path.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: add buttonRadius option and escape _build interpolations",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-17T08:32:25Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "7a4ff47ff0907c312df31f2aeb940924bd9e9f9d",
"body": "Ship the in-place artist/artwork reconciliation from #14.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "release: 1.21.0 (loadTrack metadata reconciliation)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-10T13:49:47Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "dd3ca864aa0ddd93c8486c5280bcb2b6236309d9",
"body": "Make loadTrack() a complete in-place track swap: add, update, or remove\nartist text and artwork (and keep artworkAlt in sync) rather than only\nupdating pre-existing elements. Extracts syncArtist/syncArtwork helpers,\nand binds the artwork error fallback through the abort signal so destroy()\ntears it down. Rebuilt dist.\n\nCo-authored-by: Jerry Jones <jones.jeremydavid@gmail.com>\nCo-authored-by: Codex <codex@openai.com>\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: make loadTrack reconcile track metadata",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-10T13:42:09Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "c35e557cd275c5c1ec663286d04199e3df0be345",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: date changelog entry for the localizable-strings release",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-04T20:30:05Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "b86f69288a9b4a088d12d7e0248cd5fbed699dfd",
"body": null,
"is_bot": false,
"headline": "release: 1.20.0 (localizable UI strings)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-04T18:16:16Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9e442882acc8f19a5250ae69eba1989a9641ca30",
"body": "Follow-up to seekValueText (#13): the last hardcoded English strings are\nnow options (each also settable via data-*), so non-English UIs can\ntranslate every screen-reader / lock-screen string.\n\n- playPauseLabel — play button aria-label (default 'Play/Pause')\n- speedLabel — speed button +\n[…]\nprevious output, so existing behavior is unchanged. Updates\nindex.d.ts, CHANGELOG, and adds unit + integration tests (94 passing).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(i18n): make remaining UI strings localizable",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-04T17:00:24Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "7b6312692167dbca434e2ff93aa45d7d32b52458",
"body": "The seek slider's spoken `aria-valuetext` was hardcoded to English\n`${current} of ${duration}`, leaving consumers no way to translate it.\n\nAdd a `seekValueText` option (and matching `data-seek-value-text`\nattribute) that templates the value text with printf-style placeholders —\n`%1$s` current time, \n[…]\n so\nexisting output is byte-for-byte unchanged. The library still formats the\ntimes, so consumers only supply a translated string.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: add seekValueText option to localize seek slider aria-valuetext",
"author_name": "Jerry Jones",
"author_login": "jeryj",
"committed_at": "2026-07-02T16:41:23Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "f14c214a60d20cddf796ae0fb641622cd3fe85e3",
"body": "demo/ was never published (not in package.json files[]) and is superseded by the external previews + the live site. CLAUDE.md also gains the seek/Range host requirement and the new waveform-editor sibling.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore: remove unused in-repo demo/ harness + refresh CLAUDE.md",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-02T06:46:53Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "4332fa74d5ec4514a3ba378560ecdfb32df6a8c3",
"body": "…published)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: changelog heading 1.19.2 → 1.19.3 (1.19.2 was bumped but never …",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T19:48:51Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "1c794a94753537605545dcffa404fadf5da16702",
"body": null,
"is_bot": false,
"headline": "1.19.3",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T19:44:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "77f9d03f33f86664da045cb88d1a72b562219633",
"body": null,
"is_bot": false,
"headline": "1.19.2",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T19:43:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5ab07e51e28d4b1b231db9016d0a4788fae7aac9",
"body": "During a seek-drag both the current-time readout and the hover tooltip showed the same target time. The readout now owns the live scrub time and the duplicate tooltip is suppressed while dragging.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(core): stop the scrub time showing twice (readout + tooltip)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T19:41:16Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "650d1b20d4d766eb147aaccdcd44c4126f779c8b",
"body": null,
"is_bot": false,
"headline": "1.19.1",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T16:37:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1a319b1ec46c33045a58ca5d9010adf52e13b122",
"body": "…v (#11)\n\nrole=menu + menuitemradio + aria-checked + arrow/Home/End/Escape navigation and focus management — keeps the custom styling instead of a native <select>.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(a11y): playback-speed menu is a full ARIA menu with arrow-key na…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T16:31:40Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "c6d529586ca56ef00e3181dc002f920861f40c2c",
"body": "…ck-screen hijack\n\nnavigator.mediaSession is a global singleton; registering handlers at load let the last-loaded player capture the lock-screen play/pause/seek. Now the playing player re-registers handlers + metadata on play.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: claim Media Session on play, not at load — stops multi-player lo…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T16:25:59Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "1742bd03354a49abdb23af04280d1164c5339b23",
"body": null,
"is_bot": false,
"headline": "1.19.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T16:17:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "90635412fd6c1ad43f635ef85a11dc78d8449dca",
"body": "onNextTrack/onPreviousTrack register lock-screen skip-track buttons; suppress the synthetic post-drag click that could snap the playhead. Also promotes the stale [Unreleased] changelog to [1.18.0].\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: Media Session track navigation + drag-seek double-fire fix",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T16:16:45Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "8c51c95389d471c95e1ada92bed935477a42529f",
"body": "…sion\n\n1) The waveform slider swallowed Space (default: return) while the container Space handler only fires when the root is focused — so Space did nothing when the waveform itself had focus. Slider now toggles play on Space. Reported by @jeryj.\n\n2) Media Session metadata was set once at load; iOS \n[…]\naybackState was never set — blank lock-screen card. Now re-asserts metadata + sets playbackState + setPositionState on play/pause.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: Space toggles play on the focused waveform (#10) + iOS Media Ses…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T16:09:08Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "b195c30df0ebeed032116a0e98d3842ef7034398",
"body": "…formplayer.com)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: slim README to a minimal docs-funnel (details live at docs.wave…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T12:12:42Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "7356ada07c66ad1cf820e69de0a42f1a30159586",
"body": null,
"is_bot": false,
"headline": "1.18.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T10:18:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "33c6924b711ef37ac9a4bf9b5392cbb190a6def5",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat!: move DOM chrome colours to CSS variables (breaking)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T10:06:59Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "9668d9ad23d8fc63695c96c03743b3741c84ac23",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat!: rename `subtitle` option to `artist` (breaking)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T09:49:04Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "ca4af51874f1f1929a44f2dbc46d77e5fa7c7467",
"body": "waveformColor/progressColor stop arrays now render along a configurable axis —\nwaveformGradient: 'vertical' (default), 'horizontal' (a hue sweep across the\nwhole waveform) or 'diagonal'; also data-waveform-gradient. Threaded through\nmakeFill (canvas width + direction) across every draw style.\n\nAlso \n[…]\nis what the bar's classic seekbar uses), an empty\nstop array degrades gracefully, and a malformed stop no longer aborts the frame.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(player): configurable gradient direction (waveformGradient)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T09:26:34Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "e4c78025564c6b27a4c8c8af887eab364b46ffdc",
"body": "Dragging to seek now updates the time readout and shows a tooltip live at the\ndrag position instead of only on release. The tooltip element is always created\n(so any waveform style can show the drag target); showHoverTime still gates the\nhover reveal, and the seekbar handle stays seekbar-only. Audio still commits the\nseek once, on release.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(player): live time + tooltip while scrubbing (Spotify-style drag)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T08:28:11Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "443b777669dae04f08ce36aa585e2a3719b19aec",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: promote CHANGELOG [Unreleased] to [1.17.0]",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T08:12:29Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "eaf3f617e22a820de923269844f765bb04dd6ecb",
"body": null,
"is_bot": false,
"headline": "1.17.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T07:29:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "101e81af2cdb13517818313d2d559f126a0bc3a9",
"body": "The speed toggle now sets aria-haspopup/aria-expanded (open/closed state is\nannounced to assistive tech), closes on Escape, and returns focus to the\ntrigger after a rate is picked. Kept deliberately lightweight — a disclosure,\nnot an ARIA menu: the options stay plain buttons in the tab order, no\nrole=\"menu\"/roving-tabindex machinery.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(a11y): playback-speed menu announces state + closes on Escape",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T07:15:11Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "66bbd325a1bd136df4aacce9454598b5e3f037d1",
"body": "Clicking/activating the play button — or any interactive control (slider,\nlink, input) — now keeps focus on that control. The container's click\nhandler only takes focus when the click lands on a non-interactive area,\nvia an `INTERACTIVE_ELEMENTS` closest() guard.\n\nThe keyboard-shortcut handler is in\n[…]\ned in #10 — took the focus-steal fix directly, left the broader\nkeyboard-shortcut rescoping (unverifiable Space double-fire path).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: activating a control no longer steals focus onto the player wrapper",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T07:03:02Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "2f962efb92639537dabeb441f4b7132fd7ea2347",
"body": "`seekHandle` was added as a JS option but not the data-* contract. Add\n`setBool('seekHandle')` to parseDataAttributes so declarative players can\nopt into the seekbar handle via `data-seek-handle`, matching the option.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(player): wire data-seek-handle attribute (two-paths parity)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-07-01T06:21:56Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "5edd3f7ca5fece74723be1c8618bba3d541e6074",
"body": "- Drag-to-scrub: pointer-capture drag shows a visual preview and commits the\n seek on release, so audio keeps playing instead of re-seeking every move.\n- Opt-in circular seek handle on the 'seekbar' style (`seekHandle`, default\n off; the bar enables it). DOM handle with hover-expand; removed canva\n[…]\nuge arrays) and share a `DEFAULT_SAMPLES` (1800) constant so\n extractPeaks / generateWaveform / generateWaveformData can't drift.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: seekbar drag-to-scrub + opt-in seek handle; harden peak extraction",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-30T19:40:04Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "3c2cc71144fe5da43f35c9386abead459b0f86db",
"body": null,
"is_bot": false,
"headline": "1.16.1",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-30T14:22:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "60c9f00f20ea986e6a76efb5c2e604aade538268",
"body": null,
"is_bot": false,
"headline": "1.16.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-30T12:08:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c6b701f7b8c2605114bd921167c6b7b8fdc0989c",
"body": "- Hover-time: `showHoverTime` (previously a parsed no-op) now shows a tooltip\n that follows the pointer over the waveform with the time at that position,\n in self and external modes. Built lazily into `.waveform-container`.\n- Active markers: the player drives `setActiveMarker()` from the progress \n[…]\n-syncs when\n markers re-render.\n- Tests for active-marker tracking, the hover-time element, and the artwork\n fallback (73 pass).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: hover-time tooltip + auto-highlighting active markers",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-30T12:05:16Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "92bbb6e6defd8587e8c6259314a2e8ee49361cc4",
"body": "When the `artwork` image fails to load (404 / broken), the player now swaps\nin a muted music-note placeholder tile (inline SVG data-URI) instead of the\nbrowser's broken-image icon. Logged under [Unreleased].\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: artwork fallback for broken cover URLs",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-30T11:55:22Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "8c5e386a0815e622bf50f7593bf81b10e38d1b11",
"body": "The hand-written type file annotated `height` as @default 60 and\n`barRadius` as @default 0, but the runtime DEFAULT_OPTIONS are 64 and 1\n(the only two of the ~30 annotated defaults that had drifted). Corrected\nso IDE hover tooltips match actual behaviour. Documentation-only — no\ntype or runtime change. Logged under [Unreleased]; ships with the next bump.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(types): sync index.d.ts @default JSDoc with DEFAULT_OPTIONS",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-29T20:32:07Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "3c205ae884bc2e2b9d539f425e19b141022fa2d4",
"body": "- extractPeaks scans every frame (was ~1/10) so live decode matches the\n WaveformGen offline output exactly and the shape is stable across counts.\n- samples default 256 → 1800 (live-decode source resolution; cost-neutral with\n the full scan; keeps wide/high-DPI waveforms crisp). Override via samples /\n data-samples. index.d.ts synced.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "1.15.0 — accurate every-frame peak extraction + samples default 1800",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-29T19:42:10Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "e3eda644862f2fce82df083f692cb3b22cb070cf",
"body": null,
"is_bot": false,
"headline": "1.14.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T15:49:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b440134ced8ab6619f406e0744b7a2fe941e99ab",
"body": "Adds buttonSize (+ data-button-size): number = px, string = verbatim. Sets a\n--wfp-btn-size CSS var; .waveform-btn box+glyph and .waveform-btn-minimal\nbox+glyph all derive from it via calc(), so a single value scales circle AND\nminimal proportionally (no per-style magic numbers). Default reproduces 36px.\nFolds in the minimal-glyph specificity fix. 70 tests pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: buttonSize option — one value scales both button styles",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T15:17:58Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "134c8bf74d4045aa8922e56b43b979e87b90fe67",
"body": ".waveform-btn svg {16px} had equal specificity + later source order than\n.waveform-btn-minimal svg, so it always won — every prior size change was a\nno-op. Use the compound selector .waveform-btn.waveform-btn-minimal svg (34px).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(css): minimal button glyph was pinned at 16px by a specificity tie",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T15:01:40Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "524ce96edf0e810f3a4ac22a142aaa6e0612ec5c",
"body": null,
"is_bot": false,
"headline": "1.13.1",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T14:45:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d1c983bb5677d08f01cb8eb9817962b7629636c9",
"body": "The bare-glyph minimal button (buttonStyle: minimal) read smaller than the\ndefault circle — bumped the glyph to 36px in a 2.5rem fixed box (same visual\nfootprint as the circle, fixed width so the waveform never shifts).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(css): minimal play button glyph now matches the circle button size",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T14:44:38Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "6aabee798105b0af5644bfed8a78e50867edcd09",
"body": null,
"is_bot": false,
"headline": "1.13.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T14:04:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9fe9758f92efada53f6ec06eed2c8afcaada3b18",
"body": "Auto-themed players now re-detect the page theme and redraw on a runtime\nlight/dark switch (class/data-theme on html/body, or prefers-color-scheme),\nvia one shared MutationObserver + matchMedia. Adds public refreshTheme().\nExplicit presets/colours untouched. 66 tests pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: live theme switching — re-detect light/dark at runtime",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T13:55:13Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "29a64a9f6f888eee928bf0f49001ae9ff6376ff1",
"body": null,
"is_bot": false,
"headline": "1.12.1",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T13:34:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2b55414309a073d7c56e0b464bcc86b98f1916c6",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: larger minimal button glyph (2.25rem box / 28px icon)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T13:33:05Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "1b00e508bca3763eff9106d7879d04340d56fbc0",
"body": null,
"is_bot": false,
"headline": "1.12.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T13:14:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "af813fc4409deb3fbfeb11dd79f691ef130ca502",
"body": "Add a 'bpm' option (data-bpm) to show a known tempo in the badge without\ndecoding audio (sample packs). Fix the buttonStyle:'minimal' button: it was\nauto-width, so swapping the play glyph (nudged 1px) for pause changed its\nwidth and re-sampled the adjacent waveform on every toggle — now a fixed box.\n64 tests pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: add bpm option; fix minimal button shifting waveform on play/pause",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T13:11:11Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "8b3b499c001c48be6ce93d1e36c1770791e2a99c",
"body": null,
"is_bot": false,
"headline": "1.11.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T12:38:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e1508f8e24d0c80773ae9eb7a5d590638b1ffbc7",
"body": "…s, h64)\n\nmirror STYLE_DEFAULTS now {barWidth:2, barSpacing:2} for distinct thin bars;\ndefault barRadius 1 (soft caps), samples 256 (source fidelity, resampled to the\nbar pitch), height 64. Explicit per-option values are unaffected. Backfill\nCHANGELOG for 1.9.0 / 1.10.0 / 1.11.0. 62 tests pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: refresh default look (mirror 2px spacing, soft caps, 256 sample…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T12:36:26Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "82d313b4cf3d0b3d6a36f7c2c05c7a61cb465229",
"body": "buttonStyle: 'circle' | 'minimal' (+ data-button-style). 'minimal' drops the\ncircle border/background for a bare play/pause glyph — the look sample-pack and\nbeat stores use in preview grids. Composes with the preview layout. 62 tests\npass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: add 'minimal' button style (bare glyph, no circle)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T12:28:24Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "e4e693c8410039ffee7a055ef8dd7fd152f74326",
"body": null,
"is_bot": false,
"headline": "1.10.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T12:16:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9dc1748c2d581639ed61068bbf3d7c8464eda6a0",
"body": "…ome)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore: drop the violet example from the accent comment (fully monochr…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T12:16:07Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "0a928571252528f48a7f626cabdec0d19b4ee9c8",
"body": "Go full shadcn by default: the keyboard-focus ring and speed-menu active\nstate are now neutral (a new --wfp-accent CSS var, default zinc) instead of\nviolet. Set --wfp-accent to a brand hue to re-tint (the classic violet is a\none-liner).\n\nAdd a 'preview' layout (layout option + data-layout=\"preview\")\n[…]\naveform and trims the meta row — ideal for sample-pack\npreviews and dense grids. Option, data-* and types included; 60 tests pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: monochrome default accent + compact 'preview' layout",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T12:13:07Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "530d1fbda2bd58ac557526a06279c81f05fa7b3f",
"body": null,
"is_bot": false,
"headline": "1.9.0",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T10:48:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f6795efe5628ce6627be50c9ede2afd8654f8dfe",
"body": "…ridge\n\nWrappers (waveform-bar, waveform-playlist) need the player's full data-*\noption surface but had no way to read it without re-implementing — and\ndrifting from — parseDataAttributes. Export it through the existing\nWaveformPlayer.utils bridge so they can parse a host element's attributes\nagains\n[…]\nthe single source of truth.\n\nAdditive and backward-compatible; update index.d.ts utils type and add a\nbridge test. Tests: 57 pass.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(utils): expose parseDataAttributes on the WaveformPlayer.utils b…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-28T10:33:12Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "a11bdf79dd4abdbe27842f96c1624b779e50d3d5",
"body": "…ublish)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: add CLAUDE.md (architecture, conventions, gotchas, ecosystem, p…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T19:44:49Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "fc339a903af06c61d5a14f56793a93e18ef36cb4",
"body": null,
"is_bot": false,
"headline": "1.8.1",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T19:01:03Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "096e8d582e4fb778ad81484396e686423a5b1b06",
"body": "loadTrack reset markers + waveformData but never reset this.options.waveform.\nmergeOptions() keeps the prior track's peaks when the caller passes none, and\nload() does `if (this.options.waveform) setWaveformData(...)` — so loading a\ntrack without peaks after one WITH peaks redrew the old waveform (a\n[…]\nzation didn't). Now reset it to the new peaks or null (regenerate), the\nsame way markers are reset. +1 regression test (55 total).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(core): loadTrack must not reuse the previous track's waveform peaks",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T18:58:00Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "78e558cca1c2643a05fbfbc13462446967b89c41",
"body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore: sync built waveform-player.css with the published 1.8.0 dist",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T18:47:57Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "c2346d107b2bd65c6986811c3d373bc3098babde",
"body": "…ntly 10)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "chore: bump dev tooling to latest (esbuild 0.28 / vitest 4 / concurre…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T14:35:33Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "6f0406a8e5f6a97850876aee8b2549865990649f",
"body": "errorText (default 'Unable to load audio') localizes/customizes the audio-load\nerror message; also via data-error-text. Escaped before render. (54 tests)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(core): customizable errorText option (escaped)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T14:11:29Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "215cc66d0226e96f8d9a33f6786d935f7b1ca12b",
"body": "…, URL/volume safety)\n\nSurfaced by the waveform-bar bulletproof audit — additive core capabilities so\nexternal controllers stop reaching into internals or shipping divergent copies:\n- setActiveMarker(index|null): core-owned .waveform-marker.active class.\n- WaveformPlayer.utils = { formatTime, extrac\n[…]\nrtist falls back to subtitle (self-consistent contract).\nindex.d.ts + CSS + tests (53). All additive; existing behavior unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(core): controller-support helpers (setActiveMarker, utils bridge…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T13:24:53Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "78380d343f0ea68a2493e0dbe64e069e61c44777",
"body": "…ayer]\n\nCohesive, scannable prefix across all 8 console.warn/error calls (bpm, utils,\nindex, core) plus the thrown container Error, so consumers can tell at a glance\nwhich library logged in a busy console. Reworded the init message to avoid\n'WaveformPlayer' twice.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "style(logging): prefix all console + thrown messages with [WaveformPl…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T12:30:02Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "7ed10c3843d97b32e1098118616c1271358a5a30",
"body": "- audio.js generateWaveform: dropped catch-and-log-then-rethrow (kept try/finally\n so the context still closes). It logged 'Failed to generate waveform' as error\n even on the recoverable placeholder path (then a warn followed), and logged the\n same error twice via the static generateWaveformData \n[…]\nth now logs exactly once at the correct level. The BPM/marker/JSON\nwarns and the remaining errors were already correct. (48 tests)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(logging): remove two redundant double-logs",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T12:28:14Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "9cbe8e831938926d034c2f9935c1c792d1f89e07",
"body": "- utils: setNum()/setJson() locals make parseDataAttributes fully uniform\n (setBool/setNum/setJson) — 8 hand-rolled parse/try-catch blocks collapsed.\n- themes: hasThemeHint(scheme) collapses the two identical 8-clause dark/light\n walls in detectColorScheme into two one-liners.\n- core: _requestSeek\n[…]\n builders\n in drawSeekbar.\n- index: isBrowser() names the three typeof-window/document SSR guards.\nNo behavior change (48 tests).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor: final helper extractions (parsing/theme/seek/capsule/SSR)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T12:25:16Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "c885ee2183444c2a748fdc4b10ded67296b568dd",
"body": "…ents\n\nReplaces 14 hand-written 'new CustomEvent(type, {bubbles:true[,cancelable],\ndetail})' + dispatchEvent blocks with one private _emit(type, detail,\ncancelable) helper that returns the event (so request-* callers keep their\ndefaultPrevented check). Guarantees a consistent bubbling event shape ac\n[…]\n/ended/timeupdate/request-*/destroy and removes the single\nlargest block of duplication in core.js. No behavior change (48 tests).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor: extract _emit() — single dispatch path for all 14 player ev…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T12:18:58Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "18abc2a53408a373d1e2e8c0d0bf5f357f7fd390",
"body": "…n toggle\n\n- utils: clamp(value, min, max) — replaces ~15 hand-rolled Math.max(min,Math.min)\n idioms across core.js (seek/volume/rate/progress/keyboard), audio.js, and\n drawing.js (drops its private clamp copy). parseBoolAttr() standardizes the 12\n data-* boolean flags onto one present-or-undefin\n[…]\nared by onPlay/onPause/setPlayingState (were 3 drifting copies).\n- Tests for clamp + parseBoolAttr (48 total). No behavior change.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor: extract clamp() + parseBoolAttr() helpers; dedup play-butto…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T12:13:49Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "0944802c138f97027973865589935be65ebc2aeb",
"body": "Filled every gap and upgraded thin blocks across core.js (38, incl. @fires on\nall 12 event dispatchers), drawing.js, audio.js, bpm.js, themes.js, utils.js,\nindex.js. No logic changes — build + 45 tests unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: comprehensive JSDoc across core src (73 blocks, comments-only)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T12:02:03Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "913d0f20cf21f07e289434edf861e2c91da07fe3",
"body": "Pulls the rgb-luminance math out of detectColorScheme into a tested,\nreusable utils helper; detectColorScheme reads cleaner. (45 tests)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "refactor: extract perceivedBrightness() helper into utils",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:52:50Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "58999ba4934cfcc5c2ef8937688e6db49243a10f",
"body": "Mirrors the style/data-style alias and the familiar <img>/<audio> 'src'\nconvention. Canonical url/data-url still works and wins if both are set.\nTests + index.d.ts + README/changelog updated (43 tests).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: src/data-src shorthand alias for url",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:50:46Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "06690b25134b5152b7691846f624b9f27751197e",
"body": "…horthand alias\n\n- The parseColorValue insertion had orphaned parseDataAttributes' JSDoc above\n the wrong function; moved it back so each function is documented correctly.\n- Add 'style' option + 'data-style' attribute as a shorthand alias for\n waveformStyle / data-waveform-style. Canonical long form still works and wins\n if both are set. Tests + index.d.ts + README updated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix(jsdoc): restore parseDataAttributes doc; feat: style/data-style s…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:47:54Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "7b88d64164f26902a4973f954dd6ec82b8d1387a",
"body": "… ~0.5KB)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: correct gzip claim to ~10KB (barRadius/gradient/lifecycle added…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:32:41Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "da346efce9c938a93a5cac8a135ea1a3f9925c38",
"body": "…etail, loadTrack autoplay)\n\n- waveformplayer:destroy event (symmetric to :ready) — listeners release refs.\n- loadTrack(..., { autoplay: false }) — load/restore/enqueue without playing.\n- waveformplayer:ended carries { currentTime, duration } and is synthesized in\n external mode at progress 1 (once\n[…]\nils. Tests for all of the above (39).\n\nResolves several 'wrapper reaches into player internals' findings from the\necosystem audit.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: lifecycle + event-contract completeness (destroy event, ended d…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:18:19Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "6fd5ce86fe1949ad58987894ecfaf4acf912515e",
"body": "…icons\n\nparseDataAttributes silently ignored several documented attributes, so\nzero-config external mode (data-audio-mode=\"external\") actually ran in\nself mode, and showMarkers/accessibleSeek/seekLabel/playIcon/pauseIcon\nwere inert on every auto-init path (plain HTML, WordPress, Astro). Now\nparsed. Surfaced by the ecosystem audit.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "fix: parse data-audio-mode + show-markers/accessible-seek/seek-label/…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:11:34Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "5552fa28663241eb84a32b4b0332dc9daae51113",
"body": "Full restructure in a shadcn/vercel monochrome style: badges, comparison\ntable, feature list, zero-config-first quick start, accurate options table\n(barRadius, gradient colors, accessibleSeek), framework packages, typed\nevents. References the new assets/og-image.svg. Fixes stale ~8KB claims.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "docs: monochrome README rewrite + OG image reference",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:10:05Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "9d320a853c303948c3900dee3b057bbcb0970d6f",
"body": "…js type hygiene\n\n- drawing.js: barRadius option rounds bar/mirror caps (roundRect with square\n fallback); waveformColor/progressColor accept an array of stops -> vertical\n canvas gradient (bars/mirror/blocks/dots). Bundle-neutral helpers.\n- utils.parseDataAttributes: parse data-bar-radius; colour\n[…]\n.svg: monochrome (shadcn/vercel) 1200x630 social image.\n- tests: drawing options coverage (every style with barRadius + gradient).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: rounded bar caps (barRadius) + gradient fills; OG image; audio.…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T11:07:28Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "65ad545c18ed426f81cee43a41cb23ca4cc4cfa9",
"body": "…verrides upstream)\n\nAdds --waveform-line-height (1.4), --waveform-body-gap (8px), and\n--waveform-track-gap (12px). waveform-bar currently force-overrides the\nhardcoded gap with `gap: 0 !important`; with these vars it can set\n--waveform-body-gap: 0 cleanly. Defaults unchanged — no visual change.\nPart of the v1.8.0 release (not yet published).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat(css): tokenize spacing as custom properties (fold waveform-bar o…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T10:52:30Z",
"body_truncated": false,
"is_coding_agent": true
},
{
"oid": "530d1527da2faaff5895cee4a1a006bcb24a7454",
"body": "31 tests covering the pure helpers (formatTime hours/negatives, generateId\nuniqueness/unicode, mergeOptions, peak extraction/normalization, color\npresets) and a jsdom integration suite that drives a real player in\nexternal mode — asserting the v1.8.0 fixes: no external-mode construct\ncrash, ARIA sli\n[…]\non destroy().\n\nprepublishOnly now runs tests before building. test/ is excluded from the\npublished tarball by the files allowlist.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "test: add vitest suite (utils, audio, themes, external-mode player)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T10:32:32Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "1445f816256e4424a35010099361f9c23f318164",
"body": "Adds shipped types and modernizes packaging without touching the\nzero-build <script>/data-attribute drop-in or the IIFE global.\n\nAdded:\n- Hand-authored index.d.ts (types export condition): full option surface,\n WaveformPlayer class API, and a typed custom-event map. Single source of\n truth the Rea\n[…]\nows on Unicode URLs.\n- formatTime handles H:MM:SS and clamps negatives.\n- Autoplay no longer emits an unhandled promise rejection.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "v1.8.0 — TypeScript defs, dual ESM/CJS build, bug-fix pass",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T10:26:06Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "55c2649a823b013845d2dac60a7e96c66d20109e",
"body": "Expose the waveform surface as an ARIA slider: role=\"slider\", focusable\nin the tab order, with aria-valuemin/max/now and a readable\naria-valuetext (\"0:30 of 2:00\") kept in sync on metadata load,\ntimeupdate, and external setProgress(). Standard slider keys when\nfocused — arrows (+/-5s), Page Up/Down \n[…]\nrent codebase from #9 (authored\nagainst 1.2.1). Resolves #8.\n\nCo-Authored-By: Ben Dwyer <275961+scruffian@users.noreply.github.com>\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "feat: accessible keyboard-operable seek slider (v1.7.2)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-06-27T09:12:42Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "610f772d70b3644bdba8c5b2c5ba2c98ba53dae5",
"body": "Bump to 1.7.1.\n\nThe 1.7.0 `main` pointed at `dist/waveform-player.js` — that's an\nIIFE bundle with no exports. SSR consumers doing\n`import { WaveformPlayer } from '@arraypress/waveform-player'`\nhit a \"does not provide an export named 'WaveformPlayer'\" error\nat module-resolution time.\n\nSwitch main to\n[…]\n\nloading.\n\nVerified by installing locally into a clean Node project and\ncalling `WaveformPlayer.getPeaksUrl()` from an ESM import.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "Fix SSR import: add exports field + point main at ESM",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-05-23T15:45:52Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "4ce884e0ff8c2836be1ce8521f0803739b81bdfe",
"body": "Add a static helper that derives a peaks-JSON URL from an audio\nURL by swapping the extension. Strict counterpart to\ngenerateWaveformData(): the latter decodes audio at runtime,\ngetPeaksUrl assumes you generated the peaks at build time (e.g.\nwith @arraypress/waveform-gen) and stored the JSON alongsi\n[…]\nlive decoding when\n`waveform` is undefined).\n\nRebuilt all three dist bundles (.js, .min.js, .esm.js) with the\nnew static included.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "v1.7.0 — WaveformPlayer.getPeaksUrl() static",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-05-23T14:56:49Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "bf8d120114462caaafe32bbaf67044055b8a319a",
"body": "Adds a new opt-in mode that turns the player into a visualization-only\nsurface. Skips its own <audio> element; play/pause/seek dispatch\ncancelable `waveformplayer:request-*` events instead. New public methods\n`setPlayingState(playing)` and `setProgress(currentTime, duration)` let\nan external control\n[…]\ne.g. @arraypress/waveform-bar 1.3+) pump state\nback into the visualization.\n\nFully additive — `audioMode` defaults to `'self'` so existing instances\nbehave exactly as before.\n\nVersion bumped to 1.6.0.",
"is_bot": false,
"headline": "feat: external audio mode (audioMode: 'external')",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-05-12T21:40:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0fd5698edb07af3edb1d62b98901178a2f8667c5",
"body": null,
"is_bot": false,
"headline": "Demo/website tweaks",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T19:20:21Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4e85e236e8aca21b19f0b065e6c64a35e616ba72",
"body": null,
"is_bot": false,
"headline": "Demo/website notice bar",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T19:18:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3826d181ac5bae76133a81c7044df9e74d106da7",
"body": null,
"is_bot": false,
"headline": "Demo/website improvements and bar examples",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T18:06:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dd9a0a4a63b29af3dc0b28ca619b491d029df447",
"body": null,
"is_bot": false,
"headline": "Demo refactoring",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T10:04:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d2e36110171953a377dbe06e823d542b866ff124",
"body": "…oaded if no markers are set via data attributes",
"is_bot": false,
"headline": "Feat: JSON waveform files can now include `markers` — automatically l…",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T09:16:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0bbca59ce264ea5546748cce90bfafce63544e01",
"body": null,
"is_bot": false,
"headline": "Bump version number",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T08:59:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4b71cbfacdcc9b29cd724b384d54d3ad59160d4d",
"body": null,
"is_bot": false,
"headline": "feat: add drawing style aliases (bar, block, dot)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T08:57:54Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e69b837f117e79af7e3b8de55840db12bc851971",
"body": null,
"is_bot": false,
"headline": "feat: add drawing style aliases (bar, block, dot)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-22T08:57:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1fbd0f0a3f1452d512f68b960b536e992b555052",
"body": null,
"is_bot": false,
"headline": "Add: `data-waveform` now accepts a URL to a JSON file ending in `.json`",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-21T20:59:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6a006dcaf50875e19997f846628d1e0097d372a6",
"body": null,
"is_bot": false,
"headline": "Revoking 1.4.0 changes (for now)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-21T20:42:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "36bd09c453bc96ca7728802b99108c73dd9c1e59",
"body": null,
"is_bot": false,
"headline": "feat: add JSON config file support (data-config)",
"author_name": "David Sherlock",
"author_login": "arraypress",
"committed_at": "2026-03-21T17:10:08Z",
"body_truncated": false,
"is_coding_agent": false
}
],
"releases_count": 35,
"commits_last_year": 174,
"latest_release_at": "2026-07-21T17:56:32Z",
"latest_release_tag": "v1.23.0",
"releases_from_tags": true,
"days_since_last_push": 8,
"active_weeks_last_year": 13,
"days_since_latest_release": 8,
"mean_days_between_releases": 2.4
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": true,
"health_percentage": 57,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "@arraypress/waveform-player",
"exists": true,
"license": "MIT",
"keywords": [
"audio",
"player",
"waveform",
"visualization",
"music",
"sound",
"html5",
"web-audio",
"media-session",
"audio-player",
"waveform-visualization",
"soundcloud",
"podcast-player",
"audio-visualization",
"bpm-detection"
],
"ecosystem": "npm",
"matches_repo": true,
"registry_url": "https://www.npmjs.com/package/@arraypress/waveform-player",
"is_deprecated": false,
"latest_version": "1.23.0",
"repository_url": "https://github.com/arraypress/waveform-player",
"versions_count": 47,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": 1,
"monthly_downloads": 112178,
"first_published_at": "2025-09-14T15:30:10.558000Z",
"latest_published_at": "2026-07-21T18:00:22.203000Z",
"latest_version_yanked": null,
"days_since_latest_publish": 8
}
]
},
"popularity": {
"forks": 7,
"stars": 33,
"watchers": 3,
"fork_history": {
"days": [
{
"date": "2025-12-14",
"count": 1
},
{
"date": "2026-02-06",
"count": 1
},
{
"date": "2026-02-07",
"count": 1
},
{
"date": "2026-04-08",
"count": 1
},
{
"date": "2026-06-28",
"count": 1
},
{
"date": "2026-06-30",
"count": 1
},
{
"date": "2026-07-27",
"count": 1
}
],
"complete": true,
"collected": 7,
"total_forks": 7
},
"star_history": null,
"open_issues_and_prs": 2
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [],
"has_llms_txt": false,
"has_dockerfile": false,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [],
"toolchain_manifests": [],
"largest_source_bytes": 98774,
"source_files_sampled": 16,
"oversized_source_files": 1,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 5206
},
"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": [],
"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": 1,
"merged_prs": 4,
"open_issues": 1,
"closed_ratio": 0.875,
"closed_issues": 7,
"closed_unmerged_prs": 7
},
"bus_factor": 1,
"bot_contributors": 0,
"top_contributors": [
{
"type": "User",
"login": "arraypress",
"commits": 169,
"avatar_url": "https://avatars.githubusercontent.com/u/22668877?v=4"
},
{
"type": "User",
"login": "scruffian",
"commits": 4,
"avatar_url": "https://avatars.githubusercontent.com/u/275961?v=4"
},
{
"type": "User",
"login": "jeryj",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/967608?v=4"
}
],
"contributors_sampled": 3,
"top_contributor_share": 0.971
},
"quality_signals": {
"has_ci": false,
"has_tests": true,
"ci_workflows": [],
"has_docs_dir": false,
"linter_configs": [],
"has_editorconfig": false,
"has_linter_config": false,
"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": 0,
"reason": "branch protection not enabled on development/release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 0,
"reason": "0 out of 1 merged PRs checked by a CI test -- score normalized to 0",
"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 1/30 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 0,
"reason": "project has 0 contributing companies or organizations -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": null,
"reason": "no workflows found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 0,
"reason": "no 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": 10,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 10,
"reason": "30 commit(s) and 3 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": null,
"reason": "packaging workflow not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": null,
"reason": "no dependencies found",
"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": null,
"reason": "No tokens found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 8,
"reason": "2 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "5aa8f4cf237805d1299be3564bdb2ebf18d0621a",
"ran_at": "2026-07-30T11:17:10Z",
"aggregate_score": 3.4,
"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-21T18:44:47Z",
"oldest_open_prs": [
{
"number": 20,
"created_at": "2026-07-29T08:24:21Z",
"last_comment_at": null,
"last_comment_author": null
}
],
"last_merged_pr_at": "2026-07-04T16:48:54Z",
"ci_last_conclusion": null,
"oldest_open_issues": [
{
"number": 19,
"created_at": "2026-07-29T08:14:29Z",
"last_comment_at": null,
"last_comment_author": null
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/arraypress/waveform-player",
"host": "github.com",
"name": "waveform-player",
"owner": "arraypress"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": "The weighted overall 54 is calibrated to 56 on the published index scale (record calibration 2026-08-02).",
"notes": [
{
"code": "overall_calibration",
"params": {
"raw": 54,
"calibrated": 56,
"calibration": "2026-08-02"
}
}
],
"value": 56,
"inputs": {
"security": 34,
"vitality": 75,
"community": 59,
"governance": 52,
"calibration": "2026-08-02",
"engineering": 44,
"ai_readiness": 61,
"weighted_overall_raw": 54
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "good",
"name": "Vitality",
"value": 75,
"weight": 0.21,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 66,
"inputs": {
"commits_last_year": 174,
"human_commit_share": 1,
"days_since_last_push": 8,
"active_weeks_last_year": 13
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 8 days ago",
"points": 28.8,
"status": "partial",
"details": [
{
"code": "push_recency",
"params": {
"days": 8
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "13/52 weeks with commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 13
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "174 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 174
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 3 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": 88,
"inputs": {
"releases_count": 35,
"latest_release_tag": "v1.23.0",
"releases_from_tags": true,
"days_since_latest_release": 8,
"mean_days_between_releases": 2.4
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "35 version tags (no GitHub releases)",
"points": 16.2,
"status": "partial",
"details": [
{
"code": "version_tags_no_releases",
"params": {
"count": 35
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 8 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 8
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~2.4 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 2.4
}
}
],
"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": "exceptional",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "maintained",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": null,
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": 14,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "last human commit 14 days ago",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_maintained",
"params": {
"days": 14
}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "moderate",
"name": "Community & Adoption",
"value": 59,
"weight": 0.17,
"metrics": [
{
"key": "popularity",
"band": "at_risk",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 33,
"inputs": {
"forks": 7,
"stars": 33,
"watchers": 3,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "33 stars",
"points": 24.4,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 33
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "7 forks",
"points": 6.5,
"status": "partial",
"details": [
{
"code": "forks",
"params": {
"count": 7
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "3 watchers",
"points": 1.7,
"status": "partial",
"details": [
{
"code": "watchers",
"params": {
"count": 3
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "good",
"name": "Community health",
"note": null,
"notes": [],
"value": 70,
"inputs": {
"has_readme": true,
"has_license": true,
"readme_badges": null,
"has_contributing": true,
"has_issue_template": false,
"has_code_of_conduct": false,
"readme_badge_services": [],
"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": "recognized license (MIT)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "MIT"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 18,
"status": "met",
"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": "excellent",
"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": 84,
"inputs": {
"packages": [
"@arraypress/waveform-player"
],
"dependents": null,
"ecosystems": "npm",
"total_downloads": null,
"monthly_downloads": 112178
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "112,178 downloads/month across npm",
"points": 67.3,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 112178,
"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": 52,
"weight": 0.23,
"metrics": [
{
"key": "maintainer_resilience",
"band": "critical",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 14,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 3,
"top_contributor_share": 0.971
},
"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 97% of commits",
"points": 0.7,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 97
}
}
],
"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 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "moderate",
"name": "Issue & PR responsiveness",
"note": "Excluded from scoring (no data or not applicable): Newcomer PR acceptance. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"newcomer_pr_acceptance"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 55,
"inputs": {
"merged_prs": 4,
"open_issues": 1,
"closed_issues": 7,
"prs_merged_7d": null,
"prs_decided_7d": null,
"prs_merged_30d": null,
"prs_decided_30d": null,
"issue_closed_ratio": 0.875,
"closed_unmerged_prs": 7,
"first_time_authors_30d": null,
"first_time_prs_merged_30d": null,
"first_time_prs_decided_30d": null
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "88% of issues closed",
"points": 36.8,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 88
}
}
],
"max_points": 42
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "4/11 decided PRs merged",
"points": 10.9,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 4,
"decided": 11
}
}
],
"max_points": 30
},
{
"key": "newcomer_pr_acceptance",
"name": "Newcomer PR acceptance",
"detail": "no first-time contributor's PR decided in 30d",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_newcomer_prs",
"params": {
"days": 30
}
}
],
"max_points": 13
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 1/30 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"verified_domain"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 56,
"inputs": {
"followers": 25,
"owner_type": "User",
"is_verified": null,
"owner_login": "arraypress",
"public_repos": 305,
"account_age_days": 3583
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "personal (user) account",
"points": 10,
"status": "partial",
"details": [
{
"code": "owner_personal",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": "not applicable to user accounts",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_applicable_to_user_accounts",
"params": {}
}
],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "25 followers of arraypress",
"points": 10.2,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 25,
"login": "arraypress"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "305 public repos, account ~9 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 305
}
},
{
"code": "account_age_years",
"params": {
"years": 9
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "exceptional",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"@arraypress/waveform-player"
],
"ecosystems": "npm",
"any_deprecated": false,
"min_days_since_publish": 8
},
"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 8 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 8
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "47 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 47
}
}
],
"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": "weak",
"name": "Engineering Quality",
"value": 44,
"weight": 0.19,
"metrics": [
{
"key": "engineering_practices",
"band": "at_risk",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 24,
"inputs": {
"has_ci": false,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"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": null,
"points": 0,
"status": "missed",
"details": [],
"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": "0 out of 1 merged PRs checked by a CI test -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "good",
"name": "Documentation",
"note": null,
"notes": [],
"value": 75,
"inputs": {
"topics": [
"audio",
"audio-player",
"audio-visualization",
"canvas",
"music-player",
"typescript",
"vanilla-javascript",
"waveform",
"web-audio-api",
"arraypress",
"audio-peaks",
"bpm-detection",
"media-session",
"podcast-player"
],
"has_wiki": true,
"homepage": "https://waveformplayer.com",
"has_readme": true,
"has_docs_dir": false,
"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": 0,
"status": "missed",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": "https://waveformplayer.com",
"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": "14 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 14
}
}
],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "at_risk",
"name": "Security",
"value": 34,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "at_risk",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Dangerous-Workflow, Packaging, Pinned-Dependencies, Signed-Releases, Token-Permissions. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"dangerous_workflow",
"packaging",
"pinned_dependencies",
"signed_releases",
"token_permissions"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 34,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 13,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 5,
"scorecard_aggregate": 3.4
},
"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 not enabled on development/release branches",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "0 out of 1 merged PRs checked by a CI test -- score normalized to 0",
"points": 0,
"status": "missed",
"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 1/30 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 0 contributing companies or organizations -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no workflows found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "no update tool detected",
"points": 0,
"status": "missed",
"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.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "30 commit(s) and 3 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 not detected",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "no dependencies found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"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": "No tokens found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "2 existing vulnerabilities detected",
"points": 6,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "exceptional",
"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"
],
"commit_weight_rule": {
"min_commits": 50,
"min_commit_share": 0.1
},
"review_only_matches": 0,
"below_threshold_exposures": [],
"assessed_self_published_locations": 4
},
"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": "moderate",
"name": "AI Readiness",
"value": 61,
"weight": 0.04,
"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": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 5206
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "CLAUDE.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "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": "75 of 100 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 75,
"sampled": 100
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "weak",
"name": "Verify loop (build / test / typecheck)",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Pinned-Dependencies. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_pinned_dependencies"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 47,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"package-lock.json"
],
"has_dockerfile": false,
"typed_language": false,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": false,
"typecheck_configs": [],
"agent_commit_share": 0.7,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0
},
"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": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"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": "70 of the last 100 commits agent-authored or agent-credited",
"points": 10,
"status": "met",
"details": [
{
"code": "agent_authored_commits",
"params": {
"count": 70,
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "no automated dependency updates observed",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_dependency_automation",
"params": {}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "no dependencies found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "moderate",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 52,
"inputs": {
"primary_language": "JavaScript",
"largest_source_bytes": 98774,
"source_files_sampled": 16,
"oversized_source_files": 1
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "JavaScript without a type-check config",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_typecheck_config_language",
"params": {
"language": "JavaScript"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "1/16 source files over 60KB",
"points": 51.6,
"status": "partial",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 16,
"oversized": 1
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? Carries a deliberately small weight: agent tooling is a real maintenance signal, but its absence must never gate the top of the scale (calibration saturates at raw 91, so 100/100 remains reachable with AI Readiness at zero)."
}
],
"classification": {
"top": [
"library"
],
"labels": [
"library"
],
"scores": {
"library": 6
},
"primary": "library",
"evidence": [
{
"tier": "distribution",
"label": "library",
"source": "registry:npm",
"weight": 6
}
],
"artifacts": [],
"confidence": "medium",
"host_extension": false,
"runs_as_process": false,
"consumed_by_code": true
},
"metrics_version": "2.5.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"
],
"report_type": "repository",
"generated_at": "2026-07-30T11:17:19.695891Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/a/arraypress/waveform-player.svg",
"full_name": "arraypress/waveform-player",
"license_state": "standard",
"license_spdx": "MIT"
}