Raw JSON report machine-readable
{
"data": {
"repo": {
"topics": [],
"is_fork": false,
"size_kb": 764,
"has_wiki": true,
"homepage": "https://sendspin.github.io/sendspin-js/",
"languages": {
"CSS": 10208,
"HTML": 15280,
"Shell": 341,
"Python": 8663,
"JavaScript": 28169,
"TypeScript": 337911
},
"pushed_at": "2026-07-20T23:44:42Z",
"created_at": "2025-12-02T10:17:46Z",
"owner_type": "Organization",
"updated_at": "2026-07-16T00:39:49Z",
"description": "TypeScript client library implementing the Sendspin Protocol",
"is_archived": false,
"is_disabled": false,
"license_spdx": "Apache-2.0",
"default_branch": "main",
"license_spdx_raw": "Apache-2.0",
"primary_language": "TypeScript",
"significant_languages": [
"TypeScript"
]
},
"owner": {
"blog": "https://www.sendspin-audio.com",
"name": "Sendspin",
"type": "Organization",
"login": "Sendspin",
"company": null,
"location": null,
"followers": 138,
"avatar_url": "https://avatars.githubusercontent.com/u/214994244?v=4",
"created_at": "2025-06-05T11:32:35Z",
"is_verified": null,
"public_repos": 21,
"account_age_days": 412
},
"license": {
"state": "standard",
"spdx_id": "Apache-2.0",
"raw_spdx": "Apache-2.0",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "3.2.1",
"kind": "patch",
"published_at": "2026-07-17T12:50:05Z"
},
{
"tag": "3.2.0",
"kind": "minor",
"published_at": "2026-06-08T09:33:29Z"
},
{
"tag": "2.0.6",
"kind": "patch",
"published_at": "2026-04-27T13:12:31Z"
},
{
"tag": "3.1.0",
"kind": "minor",
"published_at": "2026-04-22T20:24:41Z"
},
{
"tag": "3.0.3",
"kind": "patch",
"published_at": "2026-04-15T12:52:27Z"
},
{
"tag": "3.0.2",
"kind": "patch",
"published_at": "2026-04-13T11:35:32Z"
},
{
"tag": "3.0.1",
"kind": "patch",
"published_at": "2026-04-10T09:36:05Z"
},
{
"tag": "3.0.0",
"kind": "major",
"published_at": "2026-04-09T09:41:43Z"
},
{
"tag": "2.0.5",
"kind": "patch",
"published_at": "2026-03-24T17:07:27Z"
},
{
"tag": "2.0.4",
"kind": "patch",
"published_at": "2026-03-23T13:10:43Z"
},
{
"tag": "2.0.3",
"kind": "patch",
"published_at": "2026-03-06T13:55:13Z"
},
{
"tag": "2.0.2",
"kind": "patch",
"published_at": "2026-03-05T15:43:06Z"
},
{
"tag": "2.0.1",
"kind": "patch",
"published_at": "2026-02-24T08:04:16Z"
},
{
"tag": "2.0.0",
"kind": "major",
"published_at": "2026-01-26T08:56:55Z"
},
{
"tag": "1.0.1",
"kind": "patch",
"published_at": "2026-01-20T15:51:52Z"
},
{
"tag": "1.0.0",
"kind": "major",
"published_at": "2026-01-16T12:23:52Z"
},
{
"tag": "0.5.0",
"kind": "minor",
"published_at": "2025-12-23T13:55:01Z"
},
{
"tag": "0.4.5",
"kind": "patch",
"published_at": "2025-12-18T16:29:01Z"
},
{
"tag": "0.4.4",
"kind": "patch",
"published_at": "2025-12-16T10:33:13Z"
},
{
"tag": "0.4.3",
"kind": "patch",
"published_at": "2025-12-15T15:29:13Z"
},
{
"tag": "0.4.2",
"kind": "patch",
"published_at": "2025-12-11T10:51:12Z"
},
{
"tag": "0.4.1",
"kind": "patch",
"published_at": "2025-12-11T09:41:49Z"
},
{
"tag": "0.4.0",
"kind": "minor",
"published_at": "2025-12-10T10:49:34Z"
},
{
"tag": "0.3.1",
"kind": "patch",
"published_at": "2025-12-05T14:10:40Z"
},
{
"tag": "0.3.0",
"kind": "minor",
"published_at": "2025-12-05T13:57:37Z"
},
{
"tag": "0.2.0",
"kind": "minor",
"published_at": "2025-12-04T15:44:52Z"
},
{
"tag": "0.1.5",
"kind": "patch",
"published_at": "2025-12-03T10:23:00Z"
},
{
"tag": "0.1.4",
"kind": "patch",
"published_at": "2025-12-02T13:28:29Z"
},
{
"tag": "0.1.3",
"kind": "patch",
"published_at": "2025-12-02T13:23:19Z"
},
{
"tag": "0.1.2",
"kind": "patch",
"published_at": "2025-12-02T13:17:34Z"
},
{
"tag": "0.1.1",
"kind": "patch",
"published_at": "2025-12-02T13:14:47Z"
}
],
"recent_commits": [
{
"oid": "830e8f1062b44746606b368e7e8823fe565f8b3c",
"body": "Bumps\n[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)\nfrom 4.1.8 to 4.1.10.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/vitest-dev/vitest/releases\">vitest's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.1.10</h2>\n<h3> 🐞 Bug Fixes\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump vitest from 4.1.8 to 4.1.10 (#144)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:39:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a1eb21114789e3035079ff53d4deec35124652d7",
"body": "Bumps [prettier](https://github.com/prettier/prettier) from 3.8.3 to\n3.9.4.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/prettier/releases\">prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h2>3.9.4</h2>\n<ul>\n<li>Angular: Format <code>@content(nam\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump prettier from 3.8.3 to 3.9.4 (#143)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:37:44Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d35365f3e563f2ab7d39a2a8bf7c7c5dc0baf610",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.61.1 to 4.62.2.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.62.2</h2>\n<h2>4.62.2</h2>\n<p><em>2026-06-19</em></p>\n<h3>Bug \n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.61.1 to 4.62.2 (#140)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:36:39Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "599739c2bb14cdca09d3264ecd6c093dd9b082c2",
"body": "… (#141)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 7.3.1 to 7.5.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 7.3.1 to 7.5.1…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:35:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "539c60b75c4357bda26bbb748d65f2215292480f",
"body": "Bumps [actions/setup-python](https://github.com/actions/setup-python)\nfrom 5 to 6.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/setup-python/releases\">actions/setup-python's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v6.0.0</h2>\n<h2>What's Changed</\n[…]\nbled on this pull request as it has\nbeen open for over 30 days.\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/setup-python from 5 to 6 (#132)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:34:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3c6c55aee865a5d0c0e8d5ee394046ca1de1642d",
"body": "…#133)\n\nBumps\n[ludeeus/action-require-labels](https://github.com/ludeeus/action-require-labels)\nfrom 1.1.0 to 2.0.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/ludeeus/action-require-labels/releases\">ludeeus/action-require-labels's\nreleases</a>.</em></\n[…]\nbled on this pull request as it has\nbeen open for over 30 days.\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump ludeeus/action-require-labels from 1.1.0 to 2.0.0 (…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:33:15Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3e354200bcd65891945774b8aac0fbd3d0069ae0",
"body": "Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to\n7.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/checkout/releases\">actions/checkout's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v7.0.0</h2>\n<h2>What's Changed</h2>\n<ul>\n<li>blo\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/checkout from 6 to 7 (#139)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-13T13:31:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "96bcaeb8f98a1da6386a2bb2ad45b918c2aa4759",
"body": "Prevent `client/state` messages from being sent before the server\nhandshake completes. Local changes made during the handshake are\nincluded in the initial full state after `server/hello`.",
"is_bot": false,
"headline": "Defer client state until `server/hello` was received (#147)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-07-13T13:26:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d70c1932e509ca7bca5876e16e3dafe3ae6eccfd",
"body": "Every `client/state` previously resent the full player payload. Now the\nfirst send after each connect is a full snapshot and later sends are\ndeltas carrying only the changed fields, which the server merges.\nThis is specifically allowed since\nhttps://github.com/Sendspin/spec/pull/101.\n\nCloses #114",
"is_bot": false,
"headline": "Send only changed `client/state` fields after the initial state (#130)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-07-13T12:58:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bf72af0ad9172195e7d36df617c8927e24b5edd9",
"body": "Browser audio must start from a user action to play reliably across\nbrowsers.\n\n- add `SendspinPlayer.unlock()` for click and tap handlers\n- surface audio resume failures to callers\n- document usage and cover audio context lifecycle states\n\n---------\n\nCo-authored-by: Maxim Raznatovski <nda.mr43@gmail.com>",
"is_bot": false,
"headline": "Add player audio unlock (#146)",
"author_name": "Marcel van der Veldt",
"author_login": "marcelveldt",
"committed_at": "2026-07-13T12:31:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7c726b97ff8c3c7749de237e72a8698ab58f87fa",
"body": "With the proposed minimum sync standards in Sendspin/spec#104 the\ndefault `sync` mode in `sendspin-js` was no longer spec compliant.\n\nThis PR tweaks them to follow the spec.\nThere were two problems before this PR:\n- Drift was corrected with up to ±2% playback-rate changes and was\ntherefore audible\n- And startup errors could sit at 100-150 ms for tens of seconds because\na resync re-anchored its backlog forward instead of dropping it.",
"is_bot": false,
"headline": "Make default correction limits inaudible (#137)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-18T19:41:08Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "680ef5c1647138fd5c088f0c728fc8902a760de2",
"body": "A default `disconnect()` previously sent `client/goodbye` reason\n`shutdown`.\nPer spec an unexplained graceful disconnect should imply `restart`,\nmatching the server assumption when no goodbye is sent at all.\n\nCloses #106",
"is_bot": false,
"headline": "Default goodbye reason to `restart` (#131)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-05T10:29:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2080ec2de82aed3902756f2cd2e6947c6feab9f1",
"body": "Volume mapped linearly to gain, so the slider felt top heavy and quiet\nsteps were uneven. Gain now follows a perceptual `(volume/100)^1.5`\ncurve and ramps over 15ms via `setTargetAtTime` to noise on changes.\n\nCloses #105",
"is_bot": false,
"headline": "Apply perceptual volume curve to playback gain (#129)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-05T09:09:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9135ccb3bb398bf6ce9539d58d393a9c4541bf27",
"body": "The SDK dropped the server's `set_static_delay` value on restart and\nreconnect, forcing embedders to re-supply `syncDelay` on every connect.\nThe spec requires the client to persist it locally.\n\n`SendspinCore` now persists the delay via `storage` (defaults to\n`localStorage`) and restores it on construction. Initial precedence is\nexplicit `syncDelay`, then persisted, then the platform default.\n\nCloses #111",
"is_bot": false,
"headline": "Persist server-commanded static delay across restarts (#128)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-05T08:11:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3a0851317e6115ad3669bfad8891d8cc326e2481",
"body": "…st a real aiosendspin server. (#93)\n\nAdds a vitest test suite covering the SDK and an end-to-end run against\na real aiosendspin server. No changes to `src/`.\n\n## Unit tests (13 files, `tests/unit/`)\n\nclock-source, codec-support, core, decoder, decoder-codecs,\noutput-latency-tracker, protocol-handle\n[…]\narate PR before this one so all\ntests pass:\n\n- #116\n- #117\n- #118\n- #119\n- #121\n- #122\n\n---------\n\nCo-authored-by: Claude <noreply@anthropic.com>\nCo-authored-by: Maxim Raznatovski <nda.mr43@gmail.com>",
"is_bot": false,
"headline": "Adds a vitest test suite covering the SDK and an end-to-end run again…",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2026-06-04T15:20:38Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "8472d417ad464f245df3e265eb7deed08565de8b",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.60.4 to 4.61.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.61.1</h2>\n<h2>4.61.1</h2>\n<p><em>2026-06-04</em></p>\n<h3>Bug \n[…]\n4.60.4...v4.61.1\">compare\nview</a></li>\n</ul>\n</details>\n<br />\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.60.4 to 4.61.1 (#124)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-04T12:59:44Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6a85b719a81171492497cb94e58eff580349d893",
"body": "…#123)\n\nBumps\n[@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs)\nfrom 29.0.2 to 29.0.3.\n<details>\n<summary>Changelog</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md\">@rollup/plugin-commonjs's\nc\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump @rollup/plugin-commonjs from 29.0.2 to 29.0.3 (…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-04T12:36:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7e899c44f5c1365155d5186bea0c235041496044",
"body": "Bumps\n[eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier)\nfrom 5.5.5 to 5.5.6.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/eslint-plugin-prettier/releases\">eslint-plugin-prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump eslint-plugin-prettier from 5.5.5 to 5.5.6 (#126)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-04T12:35:35Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9b05a84752ee48365970134ea6eccb27fb9fccd2",
"body": "Bumps [concurrently](https://github.com/open-cli-tools/concurrently)\nfrom 9.2.1 to 10.0.3.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/open-cli-tools/concurrently/releases\">concurrently's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v10.0.3</h2>\n<p>Republish\n[…]\n concurrently since your current version.</p>\n</details>\n<br />\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump concurrently from 9.2.1 to 10.0.3 (#125)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-04T12:35:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "726fb261d6e5b5879b98ae0b9aa0f4682827e2b4",
"body": "Node 20 reached end-of-life on 2026-04-30, so the test and release\nworkflows now run on Node 24, the current active LTS.\n\nThis also unblocks #125.",
"is_bot": false,
"headline": "Run CI on Node 24 (#127)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-04T12:31:41Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "98bbea7f884766df6ab3b458902d1177d10d2a66",
"body": "PR #118 signed jumps with `Math.sign(jumpDeltaMs)` to stop oscillation\naround zero confirming as drift. That over-corrected: oscillation around\nany nonzero value never confirms either, so a sustained drift whose raw\nerror bounces ≥25ms each check reads as a permanent transient and never\nfires, even \n[…]\noothed stays high. Past\n`SUSTAIN_MS`, the `transient` diagnosis is treated as wrong and the\nbreach matures, seeded from when suppression began so jittery drift\nfires on the same budget as clean drift.",
"is_bot": false,
"headline": "Recorrect through sustained transient jitter (#122)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-03T15:56:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ced7fb018804c2d90b86a50a25e0159bebdba8a8",
"body": "The track progress getter clamped `positionMs` to `[0, track_duration]`.\nFor live streams `track_duration` is `0`, so the upper clamp pinned the\nposition to `0` and the counter never advanced.\n\nWhen `track_duration` is `0` (unbounded, e.g. live radio) the position\nis now floored at `0` only, with no ceiling.",
"is_bot": false,
"headline": "Keep live stream position advancing (#121)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-03T15:24:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "db51a3ac61f6db85c93c44e5bc2ad53b096ee00b",
"body": "… (#104)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 7.2.0 to 7.3.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 7.2.0 to 7.3.1…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-03T15:23:53Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0a3dd055d94d5ce5b78eb2cfbe62d1106ee3dab5",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.60.2 to 4.60.4.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.60.4</h2>\n<h2>4.60.4</h2>\n<p><em>2026-05-14</em></p>\n<h3>Bug \n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.60.2 to 4.60.4 (#103)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-06-03T15:23:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "283406806f5b4af6a2f2464cd4a4cf0910f72fdd",
"body": "The filter only skipped timestamps equal to the last update, so a\nbackward timestamp produced a negative `dt` that corrupts the Kalman\npredict step. `update()` now rejects any non-increasing timestamp, and\n`reset()` clears the stale `_last_update`. Matches the C++\nimplementation.",
"is_bot": false,
"headline": "Reject non-monotonic timestamps in the time filter (#119)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-03T15:12:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "b2b285d5f1125d9afdbcf34db5bcbf66a90a4654",
"body": "Transient jump confirmation signed the jump by the current error\nposition (`Math.sign(rawSyncErrorMs)`) instead of the jump direction.\nWhen sync error oscillated on one side of zero (e.g. `+50 → +10 → +50`),\nboth jumps read the same sign and got falsely confirmed as sustained\ndrift, releasing transi\n[…]\ne same-direction drift.\nSpike-and-rebound transients now correctly stay suppressed.\n\nAlso drops the now-redundant `jumpSign !== 0` guard, since\n`abs(jumpDeltaMs) >= 25` already implies a nonzero sign.",
"is_bot": false,
"headline": "Fix spurious resyncs from oscillating sync error (#118)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-03T15:03:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ca801134adac69dbc71fb531f6ea8e778383e8eb",
"body": "The recorrection monitor treated every sync-error jump as transient,\nre-arming on each check, so a sustained one-directional drift never\naccumulated the breach time needed to fire a hard resync.\n\nOnce a same-sign jump is confirmed, the pending-jump state is now kept\nrather than cleared, and the transient branch resets only the breach\ntimer. A continuing run of same-sign jumps stays confirmed and a\nsustained breach can build to a recorrection.",
"is_bot": false,
"headline": "Recorrect on sustained drift (#117)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-03T14:55:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "400fe584420af0739df2a71c2e120f6160954384",
"body": "Servers need each player's `required_lead_time_ms` and `min_buffer_ms`\nto compute per-player send-ahead since\nhttps://github.com/Sendspin/spec/pull/69 got merged.\nThe SDK now makes both of them configurable via `SendspinCoreConfig`\n(default 250ms each, matches Music Assistants internal default).\nBot\n[…]\n\n`setMinBufferMs`, matching the spec which allows clients to update these\nat any time.\n\nThe sample player gains inputs for both values, and the README documents\nthe config options and runtime setters.",
"is_bot": false,
"headline": "Add support to advertise player timing capabilities (#116)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-06-03T14:37:05Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dbc997d87ba1966c5228b884428f13a1e92015d1",
"body": "Since #92 the WS URL preserves `baseUrl`'s pathname and still appends\n`/sendspin`. Callers that pass a `baseUrl` already ending in `/sendspin`\n(e.g. Music Assistant's `serverUrl` for the Cast App) end up with\n`/sendspin/sendspin` and fail to connect. Skip the append when the path\nalready terminates in `/sendspin`.",
"is_bot": false,
"headline": "Avoid duplicate `/sendspin` in WebSocket URL (#101)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-05-11T07:24:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e28f7d634860e861492c5ef6af1296ac5f781777",
"body": "For publishing the 2.0.6 release.",
"is_bot": false,
"headline": "fix(ci): allow re-running release workflow at same version",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-27T13:04:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f0a704dde7f4d979d040147a392f17080543d260",
"body": "Releases run from `main`'s workflow regardless of the tagged ref.\nWithout this, publishing a 2.x patch from `release/2.x` would silently\ndemote npm `latest` to the older major.",
"is_bot": false,
"headline": "Publish 2.x releases with `legacy` npm dist-tag (#98)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-27T12:09:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f64929f988d95b6049e2b6022004a2081680dba4",
"body": "`WebSocketManager` retried with a fixed 5s delay and no exponential\nbackoff. This was especially limiting for the Cast App where we want to\nstop the App after the server couldn't be reached after a couple\nconnection attempts.\nThe cast App previously had a custom reconnect strategy, but that was\ninte\n[…]\nnnected` / `onExhausted` callbacks.\nDefaults switch from fixed 5s to 1s to 15s exponential with unlimited\nattempts; callers that don't pass `reconnect` see no API change beyond\nthe faster first retry.",
"is_bot": false,
"headline": "Add configurable reconnect with exponential backoff (#97)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-22T20:21:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "652df0a06b498c153236c98380a50fb80954ddd3",
"body": "Bumps [typescript](https://github.com/microsoft/TypeScript) from 6.0.2\nto 6.0.3.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/microsoft/TypeScript/releases\">typescript's\nreleases</a>.</em></p>\n<blockquote>\n<h2>TypeScript 6.0.3</h2>\n<p>For release notes, \n[…]\n/v6.0.2...v6.0.3\">compare\nview</a></li>\n</ul>\n</details>\n<br />\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump typescript from 6.0.2 to 6.0.3 (#95)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-22T20:20:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8eca848166eda16b141239a833a026d71fb4421d",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.60.1 to 4.60.2.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.60.2</h2>\n<h2>4.60.2</h2>\n<p><em>2026-04-18</em></p>\n<h3>Bug \n[…]\n4.60.1...v4.60.2\">compare\nview</a></li>\n</ul>\n</details>\n<br />\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.60.1 to 4.60.2 (#96)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-22T20:00:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5595ab89606864125ff56f1cc2caa5ba1dad6886",
"body": "Bumps [prettier](https://github.com/prettier/prettier) from 3.8.2 to\n3.8.3.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/prettier/releases\">prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h2>3.8.3</h2>\n<ul>\n<li>SCSS: Prevent trailing comma in <c\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump prettier from 3.8.2 to 3.8.3 (#94)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-22T18:43:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "756e859679b38dbdf244030a9520896c7aefd8c8",
"body": "## Summary\n\nThe `connect()` method was discarding the path component of `baseUrl`\nwhen building the WebSocket URL, using only `url.host`. This breaks\nreverse proxy setups where Sendspin is served behind a subpath.\n\nFixes #91\n\n## Changes\n\nTwo small changes in `connect()`:\n\n1. **Support relative URLs*\n[…]\nkward Compatibility\n\nFully backward-compatible — absolute URLs without a path still produce\nthe same WebSocket URL as before, since `pathname` is `/` which gets\nstripped by the trailing slash removal.",
"is_bot": false,
"headline": "fix: preserve baseUrl path in WebSocket URL construction (#92)",
"author_name": "Sander de Wildt",
"author_login": "sanderdw",
"committed_at": "2026-04-20T11:21:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a4cff99c7d32977b5f06a6f303e8149ea72ee134",
"body": "## Summary\n\nSplit the monolithic SDK into a composable architecture where the core\nprotocol/decoding layer is separated from Web Audio playback. This\nenables:\n\n- **Visualization apps** to consume the raw PCM stream without playback\noverhead\n- **Conformance tests** to test protocol handling without n\n[…]\n succeeds with `tsc --noEmit`\n\nhttps://claude.ai/code/session_018UYYEXUZVuQ2Z4Texa7W6m\n\n---------\n\nCo-authored-by: Claude <noreply@anthropic.com>\nCo-authored-by: Maxim Raznatovski <nda.mr43@gmail.com>",
"is_bot": false,
"headline": "Refactor SDK into modular core/player architecture (#82)",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2026-04-20T09:27:32Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "7f388ca24d897c77229e71d8831c68fce9b9bc0d",
"body": "The gap resync path (server timestamp discontinuity) was the only resync\npath without a cooldown check. It could fire per-chunk in the\n`processAudioQueue` while loop, causing resync cascades during startup\non Cast devices.\n\nGate the destructive `cutScheduledSources` and counter increment on\n`canUseHardResync()`, which enforces the 500ms cooldown and startup\ngrace. The chunk still schedules at `targetPlaybackTime` regardless,\npreserving correct timing for genuine discontinuities.",
"is_bot": false,
"headline": "Gate gap resync on cooldown (#90)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-15T12:35:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a1601c3a66aceefaed193c06045ebff233c80107",
"body": "Bumps [prettier](https://github.com/prettier/prettier) from 3.8.1 to\n3.8.2.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/prettier/releases\">prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h2>3.8.2</h2>\n<ul>\n<li>Support Angular v21.2</li>\n</ul>\n<\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump prettier from 3.8.1 to 3.8.2 (#88)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-15T12:31:16Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b62ba8e41c46da21e916c9fb18b4f495eba03150",
"body": "… (#87)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 7.1.1 to 7.2.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</a\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 7.1.1 to 7.2.0…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-15T12:30:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3251349a0ae7fd7070fc2f087ad665716249f6b3",
"body": "Bumps\n[actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact)\nfrom 4 to 5.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/upload-pages-artifact/releases\">actions/upload-pages-artifact's\nreleases</a>.</em></p>\n<blockquote>\n\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/upload-pages-artifact from 4 to 5 (#86)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-04-15T12:28:14Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "70477a80bce8de5e52feae8ca705cd3e17c50834",
"body": "The precision-based tiers (1.5/1/0.5s) caused unnecessarily short\nhorizons during the start of the playback on Cast devices.\n\nNow scheduling is always fixed to 1.5s to prevent stuttering on some\ndevices while the clock hasn't converged yet (like on my Google Home\nMini).",
"is_bot": false,
"headline": "Use fixed 1.5s scheduling horizon on Cast (#89)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-15T12:27:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ed3480eeadb8c252adc1583c4fbcad90985b7854",
"body": "Keep Cast players on the estimated audio clock instead of trying to\npromote to `getOutputTimestamp()`.\nThis avoids audible rate oscillations on Nest Hubs.",
"is_bot": false,
"headline": "Disable output timestamps on Cast (#85)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-13T15:28:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c9388a6a1d098d5c9304d850f25838ffb7fd588f",
"body": "Keep locally buffered audio running WebSocket connection drops instead\nof clearing playback immediately:\n- `SendspinPlayer` now defers the hard reset until the buffered runway\nis exhausted\n- `AudioProcessor` keeps small scheduling horizons from already-decoded\naudio so queued data continues to play while reconnecting",
"is_bot": false,
"headline": "Reduce playback gaps after disconnects (#84)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-13T11:13:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "22211e2771a5826a922caa4ef65effec0358ad97",
"body": "When running on Cast receivers, `AudioProcessor` now uses a much shorter\nscheduling horizon.\nThis seams to reduce stuttering and disconnects, especially on the Nest\nHub.\nTotal buffer size stays the same; the scheduling horizon just limits how\nmuch in advance audio is passed to the browser’s playback pipeline.",
"is_bot": false,
"headline": "Reduce scheduling horizon on Cast (#83)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-13T07:18:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c76757cfd1b1dd3bda4fedb66e8bedde9f97761e",
"body": "This PR fixes a receiver-side sync issue that could produce large\nhard-resync bursts during startup, especially on weaker runtimes.\n\n## Summary of Changes\n- validates `getOutputTimestamp()` against a fresh `performance.now()`\nsample and tolerate small jitter <- fixes timestamp jumps on chromium\nbase\n[…]\nd audio\ncontinue on the old timing base\n- adds startup grace and a short cooldown for hard re-sync so large sync\nerrors fall back to smooth rate correction instead of repeatedly cutting\nover per chunk",
"is_bot": false,
"headline": "Fix frequent timestamp cutovers and re-syncs (#81)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-10T09:29:48Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f0e4daccd93228682582c89d232341b83e13ddb0",
"body": "With #79, the fixed time filter now has vastly different thresholds,\ncausing syncing to never happen for the first 20s in the sync mode.\n\nInstead of increasing them, this PR removes the mechanism completely,\nsince:\n- We now use a burst strategy, which has better syncing already\n(implemented in #57)\n- A follow up PR will improve re-syncing, so doing more frequent syncs\nafter starting playback is possible",
"is_bot": false,
"headline": "Remove clock precision gating (#80)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-10T08:49:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6ca9517bc94661e2dd5056aca23ba99968d82f8c",
"body": "## Summary\n- Fix drift covariance initialization: divide by dt² instead of dt\n(error propagation for finite difference)\n- Fix adaptive forgetting: use Math.abs(residual) to trigger on both\npositive and negative clock jumps\n\n## Details\nThese bugs were found by comparing the TypeScript implementation \n[…]\nmeant only positive residuals\ntriggered forgetting. Negative residuals from backwards clock jumps\nwould not trigger fast recovery.\n\nCo-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>",
"is_bot": false,
"headline": "Fix time filter drift covariance and adaptive forgetting bugs (#79)",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2026-04-09T08:37:29Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "b22e82c7fdd591ca9636f544fe98c7903e56de48",
"body": "Follow-up to #66.\n\n## Summary\n\n- Separate `nextPlaybackTime` (when audio should reach the listener)\nfrom `nextScheduleTime` (when `source.start()` fires), applying\n`syncDelay` only at the scheduling layer\n- `computeTargetPlaybackTime` no longer includes syncDelay — sync error\ncalculations are now ag\n[…]\n\n\n## Breaking changes\n\nSign convention flipped: `syncDelay` is now positive (0–5000). Old\nnegative defaults (-250, -200, etc.) are replaced with positive\nequivalents. Negative values are clamped to 0.",
"is_bot": false,
"headline": "Apply syncDelay at source.start() instead of target playback time (#77)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-09T08:34:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c17683a0c84ff248ac27e8e986560c880d05c0a5",
"body": "Remove `setDebugLogging()` (breaking API change) and ~15 debug-gated log\ncalls. Replace with a single always-on status line emitted every 10s\nwhile playing:\n\n```\nSendspin: sync=+0.3ms corr=none q=4/1.2s resyncs=0 clock=timestamp(good:89) tf=synced(err=0.2ms,drift=0.001,n=60) lat=8ms mode=sync prec=p\n[…]\n of: not playing, stuttering, silence gaps, constant\nrecorrection, and bad `getOutputTimestamp` devices. NativeOpus frame\ndrops promoted to `console.warn` so decoder issues surface without debug\nmode.",
"is_bot": false,
"headline": "Improve debug logging with periodic status updates (#78)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-04-01T17:45:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1bcc7f406cd6c1755dce62d29d5d10ed8319fec8",
"body": "Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages)\nfrom 4 to 5.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/deploy-pages/releases\">actions/deploy-pages's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v5.0.0</h2>\n<h1>Changelog</h1>\n<\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/deploy-pages from 4 to 5 (#74)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-31T09:11:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ec25bb8cb90334a0ba6f04240448dbdc459c2704",
"body": "Bumps\n[actions/configure-pages](https://github.com/actions/configure-pages)\nfrom 5 to 6.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/configure-pages/releases\">actions/configure-pages's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v6.0.0</h2>\n<h1>Chan\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/configure-pages from 5 to 6 (#75)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-31T09:10:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1d3442acd0939eaaea05159da2d0f2cbdb96c480",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.60.0 to 4.60.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.60.1</h2>\n<h2>4.60.1</h2>\n<p><em>2026-03-30</em></p>\n<h3>Bug \n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.60.0 to 4.60.1 (#76)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-31T09:09:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a9e5da595ec18ac8d91d7269d9c6cb37eb901399",
"body": "Breaking change: `syncDelay` is now flipped to match the Spec.\nAdditionally allowed values are also limited to 0-5000ms as defined in\nthe Spec. Values set outside this range will be clamped.\n\nImplements per-player delay configuration from [Sendspin spec PR\n#67](https://github.com/Sendspin/spec/pull/\n[…]\nand `supported_commands:\n[\"set_static_delay\"]`. Incoming `set_static_delay` server commands apply\nthe delay and fire the new `onDelayCommand` callback so the host app can\npersist or display the value.",
"is_bot": false,
"headline": "Add support for remote per-player delay configuration (#66)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-27T16:05:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "29f4c22f01cc8182ca36c69f6d3918bde814036e",
"body": "… (#70)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 7.0.0 to 7.1.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</a\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 7.0.0 to 7.1.1…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-27T16:01:35Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "52f57eb6a58c7a7fb474dcd55a8781aa0ee78e17",
"body": "Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3\nto 6.0.2.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/microsoft/TypeScript/releases\">typescript's\nreleases</a>.</em></p>\n<blockquote>\n<h2>TypeScript 6.0</h2>\n<p>For release notes, ch\n[…]\n--------\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>\nCo-authored-by: Maxim Raznatovski <nda.mr43@gmail.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump typescript from 5.9.3 to 6.0.2 (#72)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-27T16:00:49Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8c94088fc2cc7f88fc045bc20a29c64b64eb25af",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.59.0 to 4.60.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.60.0</h2>\n<h2>4.60.0</h2>\n<p><em>2026-03-22</em></p>\n<h3>Feat\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.59.0 to 4.60.0 (#71)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-27T15:59:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "421bf0c851a45f7c6e8270128f7144ee8e56cbda",
"body": "The sample player only checked for `localhost` and `127.0.0.1` to decide\nwhether to load the local SDK build. Accessing the dev server from a\nphone on the same network (e.g., `192.168.1.50:6001`) loaded from unpkg\ninstead.\n\nReplaced hostname matching with a port check (`location.port ===\n\"6001\"`) since the dev server port is the actual invariant. Works for\nany hostname.",
"is_bot": false,
"headline": "Fix local SDK loading when testing from LAN devices (#73)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-24T11:16:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0c660021b9f7bb84199e0d4c79c15dc16cfbbb12",
"body": "## Summary\n- stop fully tearing down media-element playback only for non-Android\nand instead always clear MediaSession playback state on disconnect\n- lazily create SDK-owned hidden audio elements when media-element mode\nis actually initialized\n- destroy SDK-owned hidden audio elements on close so An\n[…]\neep showing an active media session after disconnect\n\n## Testing\n- npm run build\n\n---------\n\nCo-authored-by: Paulus Schoutsen <balloob@gmail.com>\nCo-authored-by: Maxim Raznatovski <nda.mr43@gmail.com>",
"is_bot": false,
"headline": "Fix Android media session teardown (#67)",
"author_name": "balloob-travel",
"author_login": "balloob-travel",
"committed_at": "2026-03-23T15:16:23Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1b4191156731f24c8056aa7bd4022fa54fd511f1",
"body": "…#64)\n\nBumps\n[@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs)\nfrom 29.0.1 to 29.0.2.\n<details>\n<summary>Changelog</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md\"><code>@rollup/plugin-commonj\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump @rollup/plugin-commonjs from 29.0.1 to 29.0.2 (…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-23T13:08:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "24e644dd9fdc2935a11bf5f7128e93c36dd0d88d",
"body": "Bumps [serve](https://github.com/vercel/serve) from 14.2.5 to 14.2.6.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/vercel/serve/releases\">serve's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v14.2.6</h2>\n<h3>Patch Changes</h3>\n<ul>\n<li>7fcb924: Bump ajv to 8.\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump serve from 14.2.5 to 14.2.6 (#63)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-23T13:07:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "40a7f08365e9be682de52b9b2e75d027a3888777",
"body": "… (#65)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 6.2.0 to 7.0.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</a\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 6.2.0 to 7.0.0…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-23T13:06:28Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c56a61af330ea5283429fd05b23f6aeb809ebcdf",
"body": "Tweak `detectIsIOS` so iPadOS is counted as iOS instead of a Mac.\nThis:\n- Makes the \"direct\"/\"media-element\" mode logic equal to Music Assistant\n- Uses the iOS default delay values for iPads",
"is_bot": false,
"headline": "Use media-element playback mode for iPadOS (#69)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-23T13:06:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0bcce5479fea13d88e0e5b257a4485a16e81772c",
"body": "Different platforms and browsers need different sync delays for optimal\nplayback. Fall back to a default delay that is likely to be in sync.\n\nPreviously only the Music Assistant web player had default sync delay\nvalues, this PR moves them to `sendspin-js` so they are available for\nall users of `sendspin-js`.",
"is_bot": false,
"headline": "Add platform-specific default sync delays (#68)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-23T13:04:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d4d3580260501649698796246ab22fcd49009bdf",
"body": "## Summary\n- validate `getOutputTimestamp()` at runtime before using it for\nscheduling\n- start on the existing de-quantized `currentTime` estimator and only\npromote to the timestamp clock after enough healthy samples\n- demote back to the estimator on stale, divergent, or bad-slope\ntimestamp mappings\n[…]\nror after stop/play and can prevent playback from restarting.\nThis keeps the smoother path on devices where it is valid, but falls\nback automatically when the runtime clock mapping is not trustworthy.",
"is_bot": false,
"headline": "Fix playback on Cast devices with bad getOutputTimestamp (#61)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-06T13:54:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4576bd257dc1f92f34387cc33c55c79aaaabe057",
"body": "## Summary\nRemoved the Android-specific conditional check (`!this.isAndroid`) from\nthe `stopAudioElement()` method to allow audio element pause\nfunctionality on all platforms.\n\n## Key Changes\n- Removed the `!this.isAndroid` condition from the media-element output\nmode check\n- Simplified the conditio\n[…]\nthat the previous Android-specific workaround is no longer necessary or\nwas overly restrictive.\n\nhttps://claude.ai/code/session_017EdwF1TKc2aQGvQ5mzzvKd\n\nCo-authored-by: Claude <noreply@anthropic.com>",
"is_bot": false,
"headline": "Remove Android-specific check from stopAudioElement method (#60)",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2026-03-06T13:53:48Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "ce20a81a4883e4d031033bd6fe06f62bbcd81912",
"body": "Native Opus playback broke after stream resets (e.g. track changes)\nbecause stale WebCodecs output callbacks could correlate with new-stream\nmetadata. Switched frame tracking from a timestamp-keyed `Map` to a FIFO\nqueue and now close/recreate the `AudioDecoder` on each reset instead of\ncalling `.reset()`, which ensures no stale callbacks leak across\nstreams.\n\nAlso hardened the `opus-encdec` fallback initialization for `yarn\ndev-server`.",
"is_bot": false,
"headline": "Fix native Opus decoding across stream resets (#58)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-05T15:38:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d17c5548611c4bac48a78ecefc6b1f06e56cfb6f",
"body": "Opus playback on Firefox has audio glitches with both the native\nWebCodecs decoder and the opus-encdec fallback. Firefox now falls back\nto FLAC/PCM instead.",
"is_bot": false,
"headline": "Disable Opus on Firefox (#59)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-05T15:34:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "30181fb008f62fad89604695e35db669bcb71f33",
"body": "Improves how sync corrections are applied and how the clock estimate\nconverges.\n\nPreviously, audio was scheduled immediately when data arrived from the\nserver, so a 30s buffer meant a more stable clock took effect only after\n30s of playback.\nA re-correction monitor now detects persistent sync drift \n[…]\n-b4c9-fb98aaf1316a\"\n/>\n\nOccasionally we still have jumps with players playing 10ms out of sync,\nbut this is a lot rarer now. With these changes it decreased from every\n2 minutes to roughly every hour.",
"is_bot": false,
"headline": "Improve audio sync stability and recorrection (#57)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-03-05T10:18:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "632d15ebd0fec5e2637ffb30c5d8edd20775c7d3",
"body": "Bumps\n[actions/upload-artifact](https://github.com/actions/upload-artifact)\nfrom 6 to 7.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/upload-artifact/releases\">actions/upload-artifact's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v7.0.0</h2>\n<h2>v7 W\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/upload-artifact from 6 to 7 (#55)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-05T09:03:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "54f6d843802fb66ad62af99dd64351944b6f1546",
"body": "Bumps\n[actions/download-artifact](https://github.com/actions/download-artifact)\nfrom 7 to 8.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/actions/download-artifact/releases\">actions/download-artifact's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v8.0.0</h2>\n\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/download-artifact from 7 to 8 (#56)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-03-05T09:02:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7c23ec21816c3bd1d862c614ae2cee49268cf5c5",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.57.1 to 4.59.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.59.0</h2>\n<h2>4.59.0</h2>\n<p><em>2026-02-22</em></p>\n<h3>Feat\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.57.1 to 4.59.0 (#53)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-02-24T08:02:45Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "547e197b414feb60c7553f41aacc372d4516ceb2",
"body": "Moves the npm package from `@music-assistant/sendspin-js` to\n`@sendspin/sendspin-js` and adds `publishConfig` to ensure it publishes\nas public.",
"is_bot": false,
"headline": "Publish as @sendspin/sendspin-js (#54)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-02-24T08:02:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2513fe94d921fdef97c73ba8dee09b634e299006",
"body": "Applies a short fixed-strength fade around sample insertions and\ndeletions used for clock sync. The 8 adjacent samples are blended toward\nthe synthetic sample, smoothing the splice point without any\nper-correction mismatch detection.\n\nSupersedes #51, which also scanned for optimal splice points but was too\nCPU intensive for Cast audio devices.",
"is_bot": false,
"headline": "Reduce audible clicks from sync sample corrections (#52)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-02-23T15:29:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3e72dec7755d1132c1ca1745977c4ec6deb8cd94",
"body": "The `quality-local` mode is not meant to play in sync, therefore this PR\nremoves sample insertion and deletion completely.\nNow only hard re-syncing is done when playback is out of sync by over\n600ms, which is less distracting then consistently having small\ncorrections applied.",
"is_bot": false,
"headline": "Disable sample corrections in `quality-local` correction mode (#50)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-02-23T10:03:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2ca426c7c7d8380c0acc91e5c369c2b46460b6de",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.56.0 to 4.57.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.57.1</h2>\n<h2>4.57.1</h2>\n<p><em>2026-01-30</em></p>\n<h3>Bug \n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.56.0 to 4.57.1 (#49)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-02-23T09:06:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "223e112dcb78de269c8834529649ba18dacb06b3",
"body": "Bumps [prettier](https://github.com/prettier/prettier) from 3.8.0 to\n3.8.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/prettier/releases\">prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h2>3.8.1</h2>\n<ul>\n<li>Include available <code>printers</\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump prettier from 3.8.0 to 3.8.1 (#48)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-02-23T09:05:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "12fe9be9e2b0dccc77b4875bdd94d9d470f3e1f1",
"body": "… (#47)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 6.1.1 to 6.2.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</a\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 6.1.1 to 6.2.0…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-02-23T09:05:39Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f5c5ba780d4c5ae2678f435ad36b571919c2f696",
"body": "Adds a `trackProgress` getter that calculates real-time playback\nposition using the synchronized clock. The position is computed from the\nserver's `metadata.progress` fields combined with elapsed time since the\ntimestamp.\n\nThe sample player now displays a progress bar with current/total time.\n\n## Br\n[…]\nlayer.trackProgress;\n// { positionMs: number, durationMs: number, playbackSpeed: number } | null\n```\n\nReturns `null` when no progress metadata is available. For live streams,\n`durationMs` will be `0`.",
"is_bot": false,
"headline": "Add and fix `trackProgress` for real-time position tracking (#46)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-26T08:37:54Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8d477c02b08368fb8b0b99299ab3d0af6f77f3a8",
"body": "Renames `player_support` to `player@v1_support` in `client/hello`\nmessages to match the Sendspin protocol specification.",
"is_bot": false,
"headline": "Use spec-compliant `player@v1_support` field name (#45)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-26T08:37:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2df33c358657258bbf1f2dff85bd4ccd0b46b56f",
"body": "… (#40)\n\nBumps\n[release-drafter/release-drafter](https://github.com/release-drafter/release-drafter)\nfrom 6.1.0 to 6.1.1.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/release-drafter/release-drafter/releases\">release-drafter/release-drafter's\nreleases</a\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump release-drafter/release-drafter from 6.1.0 to 6.1.1…",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-26T08:10:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "91d6594ed03f80dfb0af96476f04fbccc656401b",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.55.1 to 4.55.2.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/rollup/rollup/releases\">rollup's\nreleases</a>.</em></p>\n<blockquote>\n<h2>v4.55.2</h2>\n<h2>4.55.2</h2>\n<p><em>2026-01-19</em></p>\n<h3>Bug \n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.55.1 to 4.55.2 (#41)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-26T08:09:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "11ae7c2367d7f161d51c64466ffa0f9706737cb3",
"body": "Bumps [prettier](https://github.com/prettier/prettier) from 3.7.4 to\n3.8.0.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/prettier/releases\">prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h2>3.8.0</h2>\n<ul>\n<li>Support Angular v21.1</li>\n</ul>\n<\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump prettier from 3.7.4 to 3.8.0 (#42)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-26T07:57:51Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f35497a9e95a1598251488a43e404a694b321adf",
"body": "Bumps\n[eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier)\nfrom 5.5.4 to 5.5.5.\n<details>\n<summary>Release notes</summary>\n<p><em>Sourced from <a\nhref=\"https://github.com/prettier/eslint-plugin-prettier/releases\">eslint-plugin-prettier's\nreleases</a>.</em></p>\n<blockquote>\n<h\n[…]\nnless you reopen the\nPR or upgrade to it yourself)\n\n\n</details>\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump eslint-plugin-prettier from 5.5.4 to 5.5.5 (#43)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-26T07:57:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c1ebeaaf90dfd643c23f77199b0e63d80bdc1be9",
"body": "Playback could drift out of sync after stopping and restarting streams\nbecause the Kalman filter retained output latency estimates across\nrestarts, biasing the clock offset.\n\nOutput latency compensation now happens at audio scheduling time instead\nof in the clock filter. Also adds generation tracking to drop stale\nframes from the async WebCodecs decoder when streams restart.",
"is_bot": false,
"headline": "Fix out-of-sync playback on stream restarts (#44)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-20T15:51:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "438a59b0ec9676f09871584b5794083e1c1f302c",
"body": "Sets the Web Audio API session type to \"playback\" on iOS 17+ devices,\nwhich allows audio to continue when the device mute switch is enabled\nand enables background playback. This is a no-op on other platforms.",
"is_bot": false,
"headline": "Allow audio playback when iOS device is muted (#39)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-16T12:22:22Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e7bac6443fe1ab8d552c391612ab6e621ca05406",
"body": "Safari aggressively throttles `setTimeout` even in active tabs, which\ncaused audio queue processing delays and playback issues.\n\nReduced the debounce from 50ms to 15ms and added a scheduling guard to\nprevent duplicate processing. Includes `queueMicrotask` and\n`Promise.resolve()` fallbacks for environments where `setTimeout` is\nunavailable.",
"is_bot": false,
"headline": "Fix Safari throttling of audio queue processing (#38)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-16T12:16:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "95ea04b68e99d6824f69250cd4bd43f9e21e2f8b",
"body": "## Summary\n- remove config overrides for output mode and Android detection\n- auto-select output mode based on audio element or mobile platform\n- update docs for audio element creation, output latency compensation,\nand hardware volume defaults",
"is_bot": false,
"headline": "Simplify output config defaults (#35)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-13T10:19:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bdaa21f11f43cf0562b8d8a1dc47c4c6b424925e",
"body": "After receiving a volume command, the SDK immediately sends a state\nupdate to confirm the change. With hardware volume (e.g., Google Cast),\nthe state update would read from hardware that hadn't applied the volume\nyet, sending back stale values.\n\nNow the SDK reports the commanded value in the immediate response and\nresets the periodic state timer to prevent a pending update from also\nsending stale data.",
"is_bot": false,
"headline": "Fix stale hardware volume in state response after volume commands (#37)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-13T10:09:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "62fed16f67d5f08985d2805099323e16a5217867",
"body": "Follow-up fixes for #34:\n\n- Add missing `clockPrecision` reset in sample player status display\n- Fix threshold gap in quality mode: when error equals exactly\n`samplesBelowMs`, it now correctly uses sample correction instead of\nfalling through to disabled rate correction\n- Disambiguate debug log labels (`syncError` vs `filterError`)",
"is_bot": false,
"headline": "Fix sync strategy edge cases and debug logging (#36)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-13T09:36:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6e94da5db23da68d226e5109e20bbe4f06044655",
"body": "Improves Syncing to avoid pitch shifts.\n\nStarting playback directly after connecting to a server was especially\ndistorted due to the aggressive correction and the not yet precise clock\noffset.\nThis PR improves this by:\n- Caching the output latency of the audio context: The latency\nintroduced by the \n[…]\n Cut scheduled audio to avoid overlapping playback of music during a\nhard sync\n- Still tries to play in sync after at most 20s in the \"synced\" mode, by\nfalling back to the not yet fully accurate clock",
"is_bot": false,
"headline": "Improve syncing strategy to reduce distortion on fresh connections (#34)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-13T09:14:33Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c81973f3302c30b9742cc3f9258af4acff7c9528",
"body": "As suggested in #29, this bundles the almost silent audio file into this\nSDK.\n\nis now encoded as 8kHz FLAC, taking up 30KB when encoded as base64. This\nis the smallest filesize I could get without introducing audible\nartifacts.\n\nThe bundled audio is automatically used on Android for the MediaSession\nworkaround.",
"is_bot": false,
"headline": "Bundle silent audio for Android MediaSession support (#32)",
"author_name": "Maxim Raznatovski",
"author_login": "maximmaxim345",
"committed_at": "2026-01-08T10:12:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e1666d3ffc66362d8351f5a3348ef36092953ba1",
"body": "Bumps [rollup](https://github.com/rollup/rollup) from 4.54.0 to 4.55.1.\n- [Release notes](https://github.com/rollup/rollup/releases)\n- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)\n- [Commits](https://github.com/rollup/rollup/compare/v4.54.0...v4.55.1)\n\n---\nupdated-dependenc\n[…]\nrect:development\n update-type: version-update:semver-minor\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump rollup from 4.54.0 to 4.55.1 (#33)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-08T09:33:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "dc89be6caade5b38194b2abb606113fb18806e2e",
"body": "Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4.\n- [Release notes](https://github.com/actions/upload-pages-artifact/releases)\n- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)\n\n---\nupdated-dependencies:\n- dependency-name\n[…]\nirect:production\n update-type: version-update:semver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/upload-pages-artifact from 3 to 4 (#30)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-05T09:57:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d56d85b05bf00328d59315a3660e040860884d23",
"body": "Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.\n- [Release notes](https://github.com/actions/checkout/releases)\n- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/actions/checkout/compare/v4...v6)\n\n---\nupdated-dependenc\n[…]\nirect:production\n update-type: version-update:semver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
"is_bot": true,
"headline": "chore(deps): bump actions/checkout from 4 to 6 (#31)",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-01-05T09:57:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5ff2963f866450700afbfd1313598885d5faca1a",
"body": "This makes the SDK pick more sensible defaults based on preferred usage. The goal is to solve as many common issues as possible.\n\n**Breaking change:** for the config, the SDK tries to pick smarter defaults.\n\n- `audioOutput` now defaults to media-element when on mobile\n- `audioOutput` is set to media-element and a media element is automatically created when needed\n- isAndroid is now automatically detected based on the user agent\n- `useOutputLatencyCompensation` not defaults to `true`",
"is_bot": false,
"headline": "Simplify init (#29)",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2026-01-05T09:55:31Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "224aee655d9b451861278964849410eebae02103",
"body": "* Block HTTP URLs when demo player is hosted on HTTPS\n\nBrowsers block mixed content (HTTP requests from HTTPS pages) for security.\nThis change:\n- Adds detection of mixed content URLs (HTTP on HTTPS page)\n- Shows a clear error message explaining why HTTP URLs won't work\n- Defaults to HTTPS protocol w\n[…]\ntion form\n- Hide status bar and all other sections\n\n* Cleanup some player stuff\n\n* More cleanup\n\n* Update CLAUDE about new features in README\n\n---------\n\nCo-authored-by: Claude <noreply@anthropic.com>",
"is_bot": false,
"headline": "Cleanup player (#28)",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2025-12-24T13:52:35Z",
"body_truncated": true,
"is_coding_agent": true
},
{
"oid": "34c65a049eb695d9b0dcb001cea0871a61ed07c7",
"body": null,
"is_bot": false,
"headline": "prettier",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2025-12-23T08:02:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9da26ebc352f64ee0a488e9b681389aec13e35d6",
"body": null,
"is_bot": false,
"headline": "Handle metadata (#27)",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2025-12-23T08:01:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f518f4a6723ad98ad43c64fda477f757c39ee179",
"body": null,
"is_bot": false,
"headline": "Document more commands in examples",
"author_name": "Paulus Schoutsen",
"author_login": "balloob",
"committed_at": "2025-12-23T07:24:08Z",
"body_truncated": false,
"is_coding_agent": false
}
],
"releases_count": 31,
"commits_last_year": 138,
"latest_release_at": "2026-07-17T12:50:05Z",
"latest_release_tag": "3.2.1",
"releases_from_tags": false,
"days_since_last_push": 1,
"active_weeks_last_year": 21,
"days_since_latest_release": 5,
"mean_days_between_releases": 12.9
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": false,
"health_percentage": 50,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "@sendspin/sendspin-js",
"exists": true,
"license": "Apache-2.0",
"keywords": [
"audio",
"streaming",
"music-assistant"
],
"ecosystem": "npm",
"matches_repo": true,
"registry_url": "https://www.npmjs.com/package/@sendspin/sendspin-js",
"is_deprecated": false,
"latest_version": "3.2.1",
"repository_url": "https://github.com/Sendspin/sendspin-js",
"versions_count": 14,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": 2,
"monthly_downloads": 2387,
"first_published_at": "2026-02-24T07:57:06.766000Z",
"latest_published_at": "2026-07-17T12:50:56.588000Z",
"latest_version_yanked": null,
"days_since_latest_publish": 5
}
]
},
"popularity": {
"forks": 6,
"stars": 23,
"watchers": 1,
"fork_history": {
"days": [
{
"date": "2025-12-02",
"count": 1
},
{
"date": "2025-12-22",
"count": 1
},
{
"date": "2026-03-18",
"count": 1
},
{
"date": "2026-04-18",
"count": 1
},
{
"date": "2026-07-07",
"count": 1
}
],
"complete": true,
"collected": 5,
"total_forks": 6
},
"star_history": null,
"open_issues_and_prs": 11
},
"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": [
"tsconfig.json"
],
"toolchain_manifests": [],
"largest_source_bytes": 37638,
"source_files_sampled": 39,
"oversized_source_files": 0,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 3499
},
"dependencies": {
"manifests": [
"package.json"
],
"advisories": {
"error": null,
"scope": null,
"source": null,
"findings": [],
"collected": false,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 0,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"npm"
],
"dependencies": [
{
"name": "opus-encdec",
"manifest": "package.json",
"ecosystem": "npm",
"version_constraint": "^0.1.1"
}
],
"all_dependencies": {
"error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"source": null,
"packages": [],
"collected": false,
"truncated": false,
"total_count": null,
"direct_count": null,
"indirect_count": null
}
},
"maintainership": {
"issues": {
"open_prs": 7,
"merged_prs": 120,
"open_issues": 4,
"closed_ratio": 0.733,
"closed_issues": 11,
"closed_unmerged_prs": 12
},
"bus_factor": 1,
"bot_contributors": 1,
"top_contributors": [
{
"type": "User",
"login": "maximmaxim345",
"commits": 74,
"avatar_url": "https://avatars.githubusercontent.com/u/30021287?v=4"
},
{
"type": "User",
"login": "balloob",
"commits": 14,
"avatar_url": "https://avatars.githubusercontent.com/u/1444314?v=4"
},
{
"type": "User",
"login": "elialbert",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/420989?v=4"
},
{
"type": "User",
"login": "marcelveldt",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/6389780?v=4"
},
{
"type": "User",
"login": "sanderdw",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/19476345?v=4"
},
{
"type": "User",
"login": "balloob-travel",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/264539348?v=4"
}
],
"contributors_sampled": 6,
"top_contributor_share": 0.804
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"deploy-pages.yml",
"pr-labels.yaml",
"release-drafter.yml",
"release.yml",
"test.yml"
],
"has_docs_dir": false,
"linter_configs": [
".eslintrc.cjs"
],
"has_editorconfig": false,
"has_linter_config": true,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"yarn.lock"
],
"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": 10,
"reason": "29 out of 29 merged PRs checked by a CI test -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 1,
"reason": "Found 2/17 approved changesets -- score normalized to 1",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 10,
"reason": "project has 9 contributing companies or organizations",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 10,
"reason": "update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 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 7 issue activity found in the last 90 days -- score normalized to 10",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": 10,
"reason": "packaging workflow detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 0,
"reason": "dependency not pinned by hash detected -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "SAST tool is not run on all commits -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "security policy file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": null,
"reason": "no releases found",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 0,
"reason": "detected GitHub workflow tokens with excessive permissions",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 0,
"reason": "17 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "830e8f1062b44746606b368e7e8823fe565f8b3c",
"ran_at": "2026-07-22T14:09:41Z",
"aggregate_score": 4.7,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": true
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-20T23:44:10Z",
"oldest_open_prs": [
{
"number": 145,
"created_at": "2026-07-07T11:36:14Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 148,
"created_at": "2026-07-13T23:44:18Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 149,
"created_at": "2026-07-13T23:44:43Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 151,
"created_at": "2026-07-20T23:43:59Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 152,
"created_at": "2026-07-20T23:44:03Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 153,
"created_at": "2026-07-20T23:44:07Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 154,
"created_at": "2026-07-20T23:44:43Z",
"last_comment_at": null,
"last_comment_author": null
}
],
"last_merged_pr_at": "2026-07-13T13:39:03Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": [
{
"number": 107,
"created_at": "2026-05-27T09:53:12Z",
"last_comment_at": "2026-06-05T10:11:13Z",
"last_comment_author": "maximmaxim345"
},
{
"number": 112,
"created_at": "2026-05-27T09:53:21Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 115,
"created_at": "2026-05-27T09:53:26Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 120,
"created_at": "2026-06-03T15:19:34Z",
"last_comment_at": null,
"last_comment_author": null
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/Sendspin/sendspin-js",
"host": "github.com",
"name": "sendspin-js",
"owner": "Sendspin"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 64,
"inputs": {
"security": 47,
"vitality": 87,
"community": 43,
"governance": 62,
"engineering": 76
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "excellent",
"name": "Vitality",
"value": 87,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "good",
"name": "Development activity",
"note": null,
"notes": [],
"value": 78,
"inputs": {
"commits_last_year": 138,
"human_commit_share": 0.59,
"days_since_last_push": 1,
"active_weeks_last_year": 21
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 1 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "push_recency",
"params": {
"days": 1
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "21/52 weeks with commits",
"points": 14.5,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 21
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "138 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 138
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 7 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"openssf_scorecard_signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 100,
"inputs": {
"releases_count": 31,
"latest_release_tag": "3.2.1",
"releases_from_tags": false,
"days_since_latest_release": 5,
"mean_days_between_releases": 12.9
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "31 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 31
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 5 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 5
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~12.9 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 12.9
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "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": 9,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "last human commit 9 days ago",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_maintained",
"params": {
"days": 9
}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "at_risk",
"name": "Community & Adoption",
"value": 43,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "critical",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 28,
"inputs": {
"forks": 6,
"stars": 23,
"watchers": 1,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "23 stars",
"points": 21.8,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 23
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "6 forks",
"points": 5.8,
"status": "partial",
"details": [
{
"code": "forks",
"params": {
"count": 6
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "1 watchers",
"points": 0,
"status": "missed",
"details": [
{
"code": "watchers",
"params": {
"count": 1
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "moderate",
"name": "Community health",
"note": null,
"notes": [],
"value": 50,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": false,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "recognized license (Apache-2.0)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "Apache-2.0"
}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
},
{
"key": "ecosystem_adoption",
"band": "moderate",
"name": "Ecosystem adoption (downloads)",
"note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"registry_dependents"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 56,
"inputs": {
"packages": [
"@sendspin/sendspin-js"
],
"dependents": null,
"ecosystems": "npm",
"total_downloads": null,
"monthly_downloads": 2387
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "2,387 downloads/month across npm",
"points": 45,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 2387,
"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": 62,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "at_risk",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 32,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 6,
"top_contributor_share": 0.804
},
"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 80% of commits",
"points": 4.4,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 80
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "6 contributors",
"points": 8.1,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 6
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 9 contributing companies or organizations",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "good",
"name": "Issue & PR responsiveness",
"note": null,
"notes": [],
"value": 71,
"inputs": {
"merged_prs": 120,
"open_issues": 4,
"closed_issues": 11,
"issue_closed_ratio": 0.733,
"closed_unmerged_prs": 12
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "73% of issues closed",
"points": 34.3,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 73
}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "120/132 decided PRs merged",
"points": 34.8,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 120,
"decided": 132
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 2/17 approved changesets -- score normalized to 1",
"points": 1.5,
"status": "partial",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": null,
"notes": [],
"value": 57,
"inputs": {
"followers": 138,
"owner_type": "Organization",
"is_verified": null,
"owner_login": "Sendspin",
"public_repos": 21,
"account_age_days": 412
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "organization-owned",
"points": 30,
"status": "met",
"details": [
{
"code": "owner_organization",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "138 followers of Sendspin",
"points": 15.4,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 138,
"login": "Sendspin"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "21 public repos, account ~1 yr old",
"points": 12,
"status": "partial",
"details": [
{
"code": "public_repos",
"params": {
"count": 21
}
},
{
"code": "account_age_years",
"params": {
"years": 1
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"@sendspin/sendspin-js"
],
"ecosystems": "npm",
"any_deprecated": false,
"min_days_since_publish": 5
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on npm",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "npm"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 5 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 5
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "14 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 14
}
}
],
"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": "good",
"name": "Engineering Quality",
"value": 76,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "good",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 84,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": true,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "5 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 5
}
}
],
"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": ".eslintrc.cjs",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".eslintrc.cjs"
}
}
],
"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": "29 out of 29 merged PRs checked by a CI test -- score normalized to 10",
"points": 20,
"status": "met",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "moderate",
"name": "Documentation",
"note": null,
"notes": [],
"value": 65,
"inputs": {
"topics": [],
"has_wiki": true,
"homepage": "https://sendspin.github.io/sendspin-js/",
"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://sendspin.github.io/sendspin-js/",
"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": null,
"points": 0,
"status": "missed",
"details": [],
"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": 47,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "at_risk",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Signed-Releases. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"signed_releases"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 47,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 17,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 1,
"scorecard_aggregate": 4.7
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection not enabled on development/release branches",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "29 out of 29 merged PRs checked by a CI test -- score normalized to 10",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 2/17 approved changesets -- score normalized to 1",
"points": 0.8,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 9 contributing companies or organizations",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "update tool detected",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "30 commit(s) and 7 issue activity found in the last 90 days -- score normalized to 10",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is not run on all commits -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "no releases found",
"points": 0,
"status": "excluded",
"details": [
{
"code": "no_data",
"params": {}
}
],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "detected GitHub workflow tokens with excessive permissions",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "17 existing vulnerabilities detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "high_risk_jurisdiction_exposure",
"band": "excellent",
"name": "High-Risk Jurisdiction Exposure",
"note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
"notes": [
{
"code": "jurisdiction_evidence_limits",
"params": {}
}
],
"value": 100,
"inputs": {
"meaning": "self-published location evidence; not nationality or citizenship",
"red_flag": false,
"exposures": [],
"policy_countries": [
"Russia",
"Iran",
"North Korea"
],
"review_only_matches": 0,
"assessed_self_published_locations": 11
},
"components": [
{
"key": "policy_exposure_multiplier",
"name": "Policy exposure multiplier",
"detail": "no confirmed policy-scope location match",
"points": 100,
"status": "met",
"details": [
{
"code": "jurisdiction_no_match",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "good",
"name": "AI Readiness",
"value": 82,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "excellent",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 0.966,
"agent_instruction_files": [
"CLAUDE.md"
],
"agent_instruction_max_bytes": 3499
},
"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": "57 of 59 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 57,
"sampled": 59
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "good",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 72,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"yarn.lock"
],
"has_dockerfile": false,
"typed_language": true,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": true,
"typecheck_configs": [
"tsconfig.json"
],
"agent_commit_share": 0.05,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0.41
},
"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": ".eslintrc.cjs",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".eslintrc.cjs"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "tsconfig.json",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "tsconfig.json"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "5 of the last 100 commits agent-authored or agent-credited",
"points": 10,
"status": "met",
"details": [
{
"code": "agent_authored_commits",
"params": {
"count": 5,
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "41 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 41,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "excellent",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "TypeScript",
"largest_source_bytes": 37638,
"source_files_sampled": 39,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "TypeScript (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "TypeScript"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/39 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 39,
"oversized": 0
}
}
],
"max_points": 55
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
"GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
"deps.dev does not index npm:@sendspin/sendspin-js@3.2.1; advisories assessed against the repository dependency graph instead"
],
"report_type": "repository",
"generated_at": "2026-07-22T14:10:12.021582Z",
"schema_version": "0.26.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/s/Sendspin/sendspin-js.svg",
"full_name": "Sendspin/sendspin-js",
"license_state": "standard",
"license_spdx": "Apache-2.0"
}