Публічний реєстр
Звіт про здоров'я програмного забезпеченнясхема 0.27.0 · метрики 2.5.0 · 2026-07-30 11:17 UTC

arraypress / waveform-player

Lightweight, customizable audio player with waveform visualization

JavaScriptMIT★ 33 зірки⑂ 7 форківз вер. 2025 р.Переглянути на GitHub ↗
ТипБібліотекаяк це визначено

arraypress/waveform-player має індекс здоров’я 56 зі 100, що відповідає смузі «Помірний». Найвищий показник — Vitality (75/100), найнижчий — Security (34/100). Останнє оновлення було 8 днів тому. Більшість нещодавньої роботи виконує один учасник.

56
загалом / 100
Помірний

Індекс здоров'я програмного забезпечення

Метрики згруповано у зважені категорії на шкалі 1–100. Загальна оцінка починається як їхнє зважене середнє, відкаліброване за розподілом публічного реєстру, тож діапазони мають перцентильний зміст; коли публічні дані активують Політику юрисдикцій високого ризику, рейтинг коригується й отримує верхню межу 34 («У зоні ризику»).

56
Винятковий93-100Верхній щабель реєстру (≈ топ-5%); відповідає практично всім перевіреним критеріям
Відмінний80-92Сильний за всіма напрямами; незначні прогалини
Добрий65-79Здоровий; прогалини обмежені та керовані
Помірний50-64Прийнятний, але з помітними прогалинами; рекомендовано перевірку
Слабкий35-49Суттєві недоліки в кількох сферах
У зоні ризику20-34Суттєві слабкі місця; впровадження потребує обережності
Критичний1-19Серйозні проблеми (покинутий, єдиний мейнтейнер, без базової гігієни)
ЖиттєздатністьСпільнота тавпровадженняСталість таврядуванняІнженернаякістьБезпекаГотовність доШІ

Профіль оцінок

Кожна вісь — окрема категорія. Форма важить більше, ніж середнє: здоровий об'єкт заповнює всю фігуру, тоді як профіль із піками та провалами означає, що сила в одному вимірі маскує ризик в іншому.

Зважений загальний бал 54 калібровано до 56 за шкалою опублікованого індексу (калібрування реєстру 2026-08-02).

Власність

David MichaelОсобистий обліковий запис
25 підписників305 публічних репозиторіївз жовт. 2016 р.

Цей репозиторій належить особистому обліковому запису. Проєкт з єдиним власником несе більший ризик безперервності, ніж підтримуваний організацією.

Пакетні екосистеми

РеєстрПакетВерсіяЗавантажень / місВерсіїОстання публікаціяТеги
npm@arraypress/waveform-player1.23.0112 178478 днів томуaudioplayerwaveformvisualizationmusicsoundhtml5web-audiomedia-sessionaudio-playerwaveform-visualizationsoundcloudpodcast-playeraudio-visualizationbpm-detection

Метрики за категоріями

Життєздатність

Чи живий проєкт — чи пишеться код і чи виходять релізи?

75Добрий · 21% загального індексу
Як обчислюється оцінка
28.8/36Свіжість push — останній push 8 дн. тому
9/36Ритм комітів — 13/52 тижнів із комітами
18/18Обсяг комітів — 174 комітів за останній рік
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Використані вхідні дані
commits_last_year174
human_commit_share1
days_since_last_push8
active_weeks_last_year13
Як обчислюється оцінка
16.2/27Випускає релізи — 35 тегів версій (без релізів GitHub)
36/36Свіжість релізів — останній реліз 8 дн. тому
27/27Ритм релізів — реліз кожні ~2,4 дн.
0/10OpenSSF Scorecard: Signed-Releases — немає даних
Використані вхідні дані
releases_count35
latest_release_tagv1.23.0
releases_from_tagsтак
days_since_latest_release8
mean_days_between_releases2,4
Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: Signed-Releases. Залишкові ваги перенормовано.

Спільнота та впровадження

Чи має проєкт користувачів, завантаження, увагу та влаштовані умови для контриб’юторів?

59Помірний · 17% загального індексу
Як обчислюється оцінка
24.4/60Зірки — 33 зірок
6.5/25Форки — 7 форків
1.7/15Спостерігачі — 3 спостерігачів
Використані вхідні дані
forks7
stars33
watchers3
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Як обчислюється оцінка
22.5/22.5README
22.5/22.5Ліцензія — визнана ліцензія (MIT)
18/18Настанови CONTRIBUTING
0/13.5Кодекс поведінки
0/7.2Шаблон issue
0/6.3Шаблон PR
Використані вхідні дані
has_readmeтак
has_licenseтак
readme_badges
has_contributingтак
has_issue_templateні
has_code_of_conductні
readme_badge_services
has_pull_request_templateні
Як обчислюється оцінка
67.3/80Щомісячні завантаження — 112 178 завантажень/місяць у npm
0/20Залежні пакети в реєстрі — ця екосистема цього не повідомляє
Використані вхідні дані
packages@arraypress/waveform-player
dependents
ecosystemsnpm
total_downloads
monthly_downloads112 178
Виключено з оцінювання (немає даних або не застосовно): Залежні пакети в реєстрі. Залишкові ваги перенормовано.

Сталість та врядування

Чи переживе проєкт своїх людей — бас-фактор, реактивність, хто за ним стоїть і як супроводжуються пакети?

52Помірний · 23% загального індексу
Як обчислюється оцінка
9/54Бас-фактор — на 1 контриб’ютор(ів) припадає половина всіх комітів
0.7/22.5Розподіл комітів — головний контриб’ютор — автор 97% комітів
4.1/13.5Широта контриб’юторів — 3 контриб’юторів
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Використані вхідні дані
bus_factor1
contributors_sampled3
top_contributor_share0,971
Як обчислюється оцінка
36.8/42Вирішення issue — закрито 88% issue
10.9/30Прийняття PR — злито 4/11 вирішених PR
0/13Newcomer PR acceptance — за 30 дн. не вирішено жодного PR від новачка
0/15OpenSSF Scorecard: Code-Review — Found 1/30 approved changesets -- score normalized to 0
Використані вхідні дані
merged_prs4
open_issues1
closed_issues7
prs_merged_7d
prs_decided_7d
prs_merged_30d
prs_decided_30d
issue_closed_ratio0,875
closed_unmerged_prs7
first_time_authors_30d
first_time_prs_merged_30d
first_time_prs_decided_30d
Виключено з оцінювання (немає даних або не застосовно): newcomer_pr_acceptance. Залишкові ваги перенормовано.
Як обчислюється оцінка
10/30Підтримка власника — особистий (користувацький) обліковий запис
0/20Верифікований домен — не застосовно до користувацьких облікових записів
10.2/25Охоплення власника — 25 підписників у arraypress
25/25Послужний список — 305 публічних репозиторіїв, вік облікового запису ~9 р.
Використані вхідні дані
followers25
owner_typeUser
is_verified
owner_loginarraypress
public_repos305
account_age_days3 583
Виключено з оцінювання (немає даних або не застосовно): Верифікований домен. Залишкові ваги перенормовано.

Супровід пакетів

100Винятковий
Як обчислюється оцінка
25/25Опубліковано й доступно — 1 пакет(ів) у npm
35/35Свіжість публікацій — остання публікація 8 дн. тому
20/20Історія версій — 47 опублікованих версій
20/20Не застарілий — активний, не deprecated і не yanked
Використані вхідні дані
packages@arraypress/waveform-player
ecosystemsnpm
any_deprecatedні
min_days_since_publish8

Інженерна якість

Чи наявні базові інженерні практики та документація?

44Слабкий · 19% загального індексу

Інженерні практики

24У зоні ризику
Як обчислюється оцінка
0/24Процеси CI
24/24Наявні тести
0/16Конфігурація лінтера
0/9.6Pre-commit-хуки
0/6.4.editorconfig
0/20OpenSSF Scorecard: CI-Tests — 0 out of 1 merged PRs checked by a CI test -- score normalized to 0
Використані вхідні дані
has_ciні
has_testsтак
has_editorconfigні
has_linter_configні
has_precommit_configні
Як обчислюється оцінка
30/30README
0/25Каталог документації
15/15Сайт документації / домашня сторінка — https://waveformplayer.com
10/10Опис репозиторію
10/10Теми — 14 тем
10/10Wiki
Використані вхідні дані
topicsaudio, audio-player, audio-visualization, canvas, music-player, typescript, vanilla-javascript, waveform, web-audio-api, arraypress, audio-peaks, bpm-detection, media-session, podcast-player
has_wikiтак
homepagehttps://waveformplayer.com
has_readmeтак
has_docs_dirні
has_descriptionтак

Безпека

Чи міцні видимі практики безпеки й ланцюга постачання, без непослабленої пов’язаності з юрисдикціями високого ризику?

34У зоні ризику · 16% загального індексу

Стан безпеки

34У зоні ризику
Як обчислюється оцінка
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
0/2.5CI-Tests — 0 out of 1 merged PRs checked by a CI test -- score normalized to 0
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 1/30 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
0/10Dangerous-Workflow — немає даних
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Ліцензія — license file detected
7.5/7.5Maintained — 30 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
0/5Packaging — немає даних
0/5Pinned-Dependencies — немає даних
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — немає даних
0/7.5Token-Permissions — немає даних
6/7.5Vulnerabilities — 2 existing vulnerabilities detected
Використані вхідні дані
sourceopenssf_scorecard
checks_evaluated13
scorecard_versionv5.5.0
checks_inconclusive5
scorecard_aggregate3,4
Виключено з оцінювання (немає даних або не застосовно): dangerous_workflow, packaging, pinned_dependencies, signed_releases, token_permissions. Залишкові ваги перенормовано.

Готовність до ШІ

Наскільки репозиторій оснащений для розробки та супроводу за участі ШІ-агентів? Має свідомо малу вагу (4%): агентний інструментарій — реальний сигнал супроводу, але репозиторій без нього все одно може отримати 100/100.

61Помірний · 4% загального індексу
Як обчислюється оцінка
45/45Інструкції для агентів — CLAUDE.md
0/15Машиночитана документація (llms.txt)
40/40Читабельна історія комітів — намір зазначено у 75 з 100 людських комітів (структурований заголовок або пояснювальний текст)
Використані вхідні дані
has_llms_txtні
legible_history_share0,75
agent_instruction_filesCLAUDE.md
agent_instruction_max_bytes5 206
Як обчислюється оцінка
0/18Розгортання однією командою
22/22Автоматизовані тести
0/11Конфігурація лінтера / форматера
0/11Статична перевірка типів
10/10Відтворюване середовище — lockfile
10/10Підтверджена практика роботи з агентами — 70 з останніх 100 комітів створено агентом або з його зазначенням
0/8Автоматизоване супроводження — автоматичних оновлень залежностей не виявлено
0/10OpenSSF Scorecard: Pinned-Dependencies — немає даних
Використані вхідні дані
has_nixні
has_testsтак
lockfilespackage-lock.json
has_dockerfileні
typed_languageні
bootstrap_files
has_devcontainerні
has_linter_configні
typecheck_configs
agent_commit_share0,7
toolchain_manifests
dependency_bot_commit_share0
Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: Pinned-Dependencies. Залишкові ваги перенормовано.
Як обчислюється оцінка
0/45Типізований код — JavaScript без конфігурації перевірки типів
51.6/55Керовані розміри файлів — 1/16 файлів вихідного коду понад 60 КБ
Використані вхідні дані
primary_languageJavaScript
largest_source_bytes98 774
source_files_sampled16
oversized_source_files1

Ключові факти

33зірок GitHub
3контриб'юторів
174комітів за останні 12 місяців
8днів від останнього пушу
35релізів
1бас-фактор
1відкритих issue
npmпакетних екосистем

Попередження щодо збору даних

  • 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

Докладніше

Історія зірок і форків 0 ★ / 7 ⇿
0Зірки
7Форки
32Релізи

Коли додано кожну зірку й форк — зібрано з GitHub і згруповано за днями. Кумулятивне зростання розміщено просто над денними додаваннями, з яких воно складається, тож їх видно одне проти одного: рівномірне органічне накопичення виглядає зовсім інакше, ніж різкий короткочасний сплеск. Там, де цю різницю можна виміряти, її подано як автентичність росту.

1234567712025-122026-042026-07
Мажорні 0Мінорні 18Патчі 14
OpenSSF Scorecard 3.4 / 10
3.4сукупно

Незалежна, не прив'язана до інструментів оцінка безпеки від відкритого проєкту OpenSSF Scorecard. Кожна перевірка винагороджує практику безпеки, а не інструмент конкретного постачальника. Перевірки, які Scorecard не зміг визначити, позначено н/д і виключено з оцінки безпеки (вони ніколи не зараховуються як нуль).Scorecard v5.5.0 · 2026-07-30 11:17 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
0CI-Tests0 out of 1 merged PRs checked by a CI test -- score normalized to 0
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 1/30 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
н/дDangerous-Workflowno workflows found
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
н/дPackagingpackaging workflow not detected
н/дPinned-Dependenciesno dependencies found
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
н/дSigned-Releasesno releases found
н/дToken-PermissionsNo tokens found
8Vulnerabilities2 existing vulnerabilities detected
Усі залежності не зібрано

Не вдалося зібрати розв'язаний набір залежностей для цього звіту: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

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

Оцінки — це сигнали, а не гарантії. Вони відображають публічно видимі практики на GitHub — це не аудит коду й не гарантія безпеки.

Відсутні дані виключаються, а ваги перенормовуються — нуль за відсутність ніколи не ставиться. Методологія версіонована й відкрита: метрики v2.5.0, схема v0.27.0 — повна методологія · вікі метрик.

Як окремий результат виглядає на тлі всього реєстру: сукупна статистикаnpm.