diff --git a/BACKLOG.md b/BACKLOG.md index 8e06101..8c7e014 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -4,9 +4,9 @@ NeClip remains a small local macOS utility: clipboard history, snippet folders and safe EN/RU layout correction. Accounts, cloud sync, subscriptions, telemetry, advertising and AI processing are not planned. -## Current public release — 2.8.7 / build 53 +## Current public release — 2.8.8 / build 54 -Released 26 September 2026 after required Swift CI, CodeQL and secret scan; Developer ID signing, Apple notarization, stapling, Gatekeeper, mounted-DMG and independent public-download checks passed. Download the [NeClip 2.8.7 DMG](https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.dmg) or [ZIP archive](https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip). See [release evidence](docs/RELEASE-2.8.7-STATUS.md). +Released 27 September 2026 after required Swift CI, CodeQL and secret scan; Developer ID signing, Apple notarization, stapling, Gatekeeper, mounted-DMG and independent public-download checks passed. Download the [NeClip 2.8.8 DMG](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg) or [ZIP archive](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip). See [release evidence](docs/RELEASE-2.8.8-STATUS.md). - Save any text history item as a raw-text snippet with folder selection and duplicate checks. - Search local history with bounded text, type, application and date filters. diff --git a/CHANGELOG.md b/CHANGELOG.md index a30c8c0..5673b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2.8.8 / build 54 — candidate +## 2.8.8 / build 54 — released 2026-09-27 - Insert snippet tokens at the caret or replace the selection; preserve native undo and isolate it between snippets. - Skip unrelated clipboard reads during snippet rendering; move required text reads off main and reject changed generations. The final rollback snapshot remains in place. diff --git a/README.md b/README.md index 8073932..ce30b06 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,22 @@ NeClip — приложение для macOS, которое сохраняет ## Скачать NeClip для Mac -Актуальная версия: **NeClip 2.8.7 (сборка 53)**. Дата выпуска — 26 сентября 2026 года. +Актуальная версия: **NeClip 2.8.8 (сборка 54)**. Дата выпуска — 27 сентября 2026 года. -- [Скачать DMG для Mac](https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.dmg) — 2 142 658 байт. -- [Скачать ZIP-архив приложения](https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip) — 2 109 242 байт. -- [Открыть страницу релиза NeClip 2.8.7](https://github.com/AffPapa/neclip/releases/tag/v2.8.7). +- [Скачать DMG для Mac](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg) — 2 146 754 байт. +- [Скачать ZIP-архив приложения](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip) — 2 111 918 байт. +- [Открыть страницу релиза NeClip 2.8.8](https://github.com/AffPapa/neclip/releases/tag/v2.8.8). Откройте DMG или распакуйте ZIP, перетащите NeClip в «Программы» и запустите. Значок приложения появится в строке меню macOS; постоянный значок в Dock не нужен. Приложение и DMG подписаны Developer ID, прошли нотариальную проверку Apple и содержат билет нотариализации. Перед установкой можно сверить контрольные суммы SHA-256: -- DMG: `3111009d22c8ad9e56a3951f215c63cfe669786ff0f8596ca8d80ed007e8984a` ([файл контрольной суммы](https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.dmg.sha256)). -- ZIP: `a7ff40101da09d16c69ac2c5f8535bd1b547a3af84308b823012c3bd8e8f169d` ([файл контрольной суммы](https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip.sha256)). +- DMG: `6bf16a132261d66926e7b327e835dd0ee526e477d19d8907b207a7f2ea164d74` ([файл контрольной суммы](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg.sha256)). +- ZIP: `e33fe900103f5999928054f4de8535ab74d8f1ceab53df6fbaf130327c12ae38` ([файл контрольной суммы](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip.sha256)). -DMG и ZIP находятся в одном неизменяемом релизе и содержат подписанное, нотарифицированное приложение. Подробности проверок — в [отчёте о выпуске 2.8.7](docs/RELEASE-2.8.7-STATUS.md). +DMG и ZIP находятся в одном неизменяемом релизе и содержат подписанное, нотарифицированное приложение. Подробности проверок — в [отчёте о выпуске 2.8.8](docs/RELEASE-2.8.8-STATUS.md). -Исходный коммит приложения: `2cad4383bcc6d1a7c8c28c6e60bbfe67a3b11be6`. +Исходный коммит приложения: `76a3884810539a5e8bdb226624e5c46e05dcc376`. **Проект Иванова:** [affpapa.org](https://affpapa.org/). @@ -86,11 +86,13 @@ DMG и ZIP находятся в одном неизменяемом релиз Инструкции используют синтетические примеры, описывают ограничения и разрешения macOS и ссылаются на официальные страницы сравниваемых продуктов. -## Изменения в NeClip 2.8.7 +## Изменения в NeClip 2.8.8 -Поиск истории сохраняет стандартную работу клавиатуры в фильтрах, кнопках и при вводе через IME; модифицированные команды навигации передаются AppKit. Отключение истории изображений учитывается и для скриншотов, уже ожидающих записи. При совпадении заголовка поиск больше не преобразует всё тело записи к нижнему регистру. +Токены сниппетов вставляются в позицию курсора или заменяют выделение; работают нативные Undo/Redo, а история отмены отделена для каждого сниппета. Смена документа обновляет поле, запоздалый ввод не переносится в другой сниппет. -Синтетический поиск по 64 записям с телами по 512 КиБ, 30 прогретых release-итераций: p50 164,965 → 3,741 мс; p95 229,571 → 4,187 мс. Это измерение поиска в SQLite и обработки строк, не полной задержки интерфейса. +История скриншотов показывает размеры готового изображения с фоном. При ошибке копирования восстанавливается прежний буфер, если его не успело изменить другое приложение. Если прежний буфер нельзя безопасно сохранить для восстановления, приложение предлагает экспорт в файл. + +Подстановка обычного текста и даты больше не читает буфер заранее; `{clipboard}` читается в фоновой очереди с проверкой поколения. Защитная копия буфера перед вставкой остаётся — это не обещание отсутствия задержек при любых данных. Фильтры истории и поле предпросмотра получили явные метки доступности. ## Разработка и выпуск diff --git a/SECURITY.md b/SECURITY.md index 466c90f..f59c617 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,7 +10,7 @@ only when the user explicitly chooses “Check for Updates”. Release binaries are accepted only after Developer ID signing, Apple notarization, stapling, Gatekeeper assessment and published SHA-256 verification. -The only supported public release is `v2.8.7`, with immutable assets and tag. +The only supported public release is `v2.8.8`, with immutable assets and tag. Older releases remain superseded and are retained for rollback. GitHub also checks Swift and workflow source with CodeQL, allows only GitHub-owned Actions referenced by full SHA and monitors the exact SwiftPM dependency for vulnerabilities. Branch diff --git a/docs/CLIPBOARD-BENCHMARK-2.8.8.md b/docs/CLIPBOARD-BENCHMARK-2.8.8.md new file mode 100644 index 0000000..0c86636 --- /dev/null +++ b/docs/CLIPBOARD-BENCHMARK-2.8.8.md @@ -0,0 +1,63 @@ +# Direct clipboard preparation microbenchmark + +Stable Xcode Swift 6.4.0.34.1, optimized `-O`, arm64, macOS 27.0 (26A5416b). 8 MiB ASCII on a unique named pasteboard; five warmups and 40 paired samples. The eager path matches the removed unconditional `string(forType:)`; the lazy path calls the production helper with a literal template. The user's general clipboard is never changed. + +| Path | p50 ms | p95 ms | Text reads per preparation | +| --- | ---: | ---: | ---: | +| Eager | 0.352375 | 0.420542 | 1 | +| Lazy literal | 0.000125 | 0.000375 | 0 | + +These measurements exclude final paste, provider delays, and the separate rollback snapshot. They do not establish end-to-end UI speedup. The temporary constant below is the unchanged `ClipboardCapturePolicy.maxTextBytes` required to compile the unused renderer default argument; no policy behavior is benchmarked. + +Save the following harness as `/tmp/neclip-clipboard-benchmark.swift`, then from the source checkout run: + +```sh +printf '%s\n' 'enum ClipboardCapturePolicy { static let maxTextBytes = 2 * 1024 * 1024 }' > /tmp/neclip-benchmark-policy.swift +DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun swiftc -O Sources/NeClip/SnippetTokenCatalog.swift Sources/NeClip/SnippetRenderer.swift Sources/NeClip/SnippetClipboardRead.swift /tmp/neclip-benchmark-policy.swift /tmp/neclip-clipboard-benchmark.swift -o /tmp/neclip-clipboard-benchmark +/tmp/neclip-clipboard-benchmark +``` + +```swift +import AppKit +import Foundation + +@main +struct ClipboardPreparationBenchmark { + @MainActor + static func main() throws { + let board = NSPasteboard(name: .init("neclip.audit.benchmark.\(UUID())")) + defer { board.releaseGlobally() } + board.clearContents() + precondition(board.setString(String(repeating: "x", count: 8 * 1024 * 1024), forType: .string)) + let generation = board.changeCount + let template = "Literal synthetic snippet" + var checksum = 0 + var before: [Double] = [], after: [Double] = [] + for iteration in 0..<45 { + let old = autoreleasepool { () -> Double in + let start = DispatchTime.now().uptimeNanoseconds + let value = board.string(forType: .string) + if value?.isEmpty == false { checksum += 1 } + return Double(DispatchTime.now().uptimeNanoseconds - start) / 1_000_000 + } + let new = try autoreleasepool { () throws -> Double in + let start = DispatchTime.now().uptimeNanoseconds + let value = try SnippetClipboardRead.readIfNeeded(template: template, expectedGeneration: generation, + readGeneration: { board.changeCount }, readText: { board.string(forType: .string) }) + precondition(value == nil) + return Double(DispatchTime.now().uptimeNanoseconds - start) / 1_000_000 + } + if iteration >= 5 { before.append(old); after.append(new) } + } + func stats(_ values: [Double]) -> [String: Double] { + let s = values.sorted() + return ["p50_ms": s[s.count / 2], "p95_ms": s[Int(Double(s.count) * 0.95) - 1]] + } + let output: [String: Any] = ["scope": "direct literal-template clipboard preparation only; excludes final paste/rollback snapshot", + "fixtureBytes": 8 * 1024 * 1024, "samples": 40, "warmup": 5, + "before": stats(before), "after": stats(after), "beforeReads": checksum, "afterReads": 0] + let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys]) + print(String(decoding: data, as: UTF8.self)) + } +} +``` diff --git a/docs/PROJECT-MAP.md b/docs/PROJECT-MAP.md index f5aa35e..f4cc6f9 100644 --- a/docs/PROJECT-MAP.md +++ b/docs/PROJECT-MAP.md @@ -1,8 +1,8 @@ # NeClip project map -Current public release: **2.8.7 / build 53**. The exact signed artifacts, +Current public release: **2.8.8 / build 54**. The exact signed artifacts, source commit and release checks are recorded in -[RELEASE-2.8.7-STATUS.md](RELEASE-2.8.7-STATUS.md). +[RELEASE-2.8.8-STATUS.md](RELEASE-2.8.8-STATUS.md). ## Runtime ownership @@ -22,7 +22,7 @@ source commit and release checks are recorded in ## Release boundary -Only `v2.8.7` is supported publicly. Its immutable GitHub release contains both +Only `v2.8.8` is supported publicly. Its immutable GitHub release contains both the signed, notarized DMG and ZIP; `docs/version.json` records their URLs, SHA-256 digests and sizes. The release tag points to the exact source commit in `main`. Verify downloaded bytes before changing either public link. diff --git a/docs/RELEASE-2.8.8-STATUS.md b/docs/RELEASE-2.8.8-STATUS.md new file mode 100644 index 0000000..79585a4 --- /dev/null +++ b/docs/RELEASE-2.8.8-STATUS.md @@ -0,0 +1,32 @@ +# NeClip 2.8.8 release evidence + +Published 2026-09-27T11:11:38Z. Immutable [v2.8.8](https://github.com/AffPapa/neclip/releases/tag/v2.8.8), build 54. + +## Source and gates + +- Exact source/tag and [PR #73](https://github.com/AffPapa/neclip/pull/73) merge: `76a3884810539a5e8bdb226624e5c46e05dcc376`. +- Required Swift CI, CodeQL Actions/Ruby/Swift and full-history Secret Scan passed before protected merge. No bypass. Previous immutable releases retained. +- Strict debug and optimized release, ASan and TSan each passed 380 XCTest (8 expected opt-in skips) plus 4 Swift Testing checks, no failures. Stable Xcode Swift 6.4.0.34.1, arm64, macOS 27.0 (26A5416b). +- Site 14 tests / 45 assertions; SEO 13 / 37. All eight canonical pages and project attribution verified locally. Source/history and release-artifact secret scans passed. + +## Fixes and evidence + +- Tokens previously appended irrespective of the caret. Native selection replacement now covers Unicode, insertion positions, autosave and document-scoped undo. Physical preview caught menu Undo and document-refresh issues; final code fixes both. Hosted SwiftUI/AppKit regression verifies observed document updates. Late native callbacks cannot overwrite another snippet. +- Original 400x300 capture exported with padding is 448x348; history now derives the latter from final bytes. Original/malformed input regressions pass. +- Failed screenshot-write injection preserves old representations; intervening external copy survives. Snapshot-limit regression preserves existing data without clearing. Native OS write failure was not observed; restoration is best-effort if the OS rejects the rollback too. +- Direct rendering clipboard reads: literal/date/escaped templates 1 on main -> 0; active token 1 on main -> 1 on data queue. Generation changes abort. The separate PasteService rollback snapshot remains on main. These are deterministic operation counts, not end-to-end latency measurements. +- [Reproducible narrow optimized benchmark](CLIPBOARD-BENCHMARK-2.8.8.md): private named pasteboard with 8 MiB synthetic ASCII, 5 warmups and 40 paired samples, same stable Swift compiler/machine. Eager preparatory read p50 0.352375 ms / p95 0.420542 ms; lazy literal-template preparation p50 0.000125 ms / p95 0.000375 ms. Excludes final copy and rollback snapshot; no general UI speedup claim. +- Physical isolated preview: token selection including emoji, multiline paste, title-focus-to-content insertion, Cmd+Z/Shift+Cmd+Z, autosave/relaunch, document switching and undo isolation passed. Explicit preview and all three history-filter AX labels observed; filtered search returned the single fixture B row, and Return on a filter did not paste/close. No claim of VoiceOver speech verification. + +## Public artifacts + +Developer ID, app/DMG notarization and stapling passed. Public ZIP app and mounted public DMG app pass signature, stapler and Gatekeeper. Both archives and their sidecars were uploaded before immutable publication; fresh downloads match local bytes, sizes and SHA-256. + +| Artifact | Bytes | SHA-256 | +| --- | ---: | --- | +| [DMG](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg) | 2,146,754 bytes | `6bf16a132261d66926e7b327e835dd0ee526e477d19d8907b207a7f2ea164d74` | +| [ZIP](https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip) | 2,111,918 bytes | `e33fe900103f5999928054f4de8535ab74d8f1ceab53df6fbaf130327c12ae38` | + +## Limits + +Snapshot bounds are 16 MiB, 32 items and 32 representations per item; screenshot copy refuses before clearing if preservation is impossible and offers file export. VoiceOver audio, live IME composition, real cross-app auto-paste, ScreenCaptureKit permissions, multiple monitors/Spaces and every supported macOS are not certified. User history and installed app were untouched. Pages/README are verified separately after metadata deploy. [Проект Иванова](https://affpapa.org/). diff --git a/docs/backlog.json b/docs/backlog.json index 9a8ef2b..40724f9 100644 --- a/docs/backlog.json +++ b/docs/backlog.json @@ -1,10 +1,10 @@ { "currentRelease": { - "version": "2.8.7", - "build": 53, - "release": "https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.dmg", - "appArchive": "https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip", - "evidence": "https://github.com/AffPapa/neclip/blob/main/docs/RELEASE-2.8.7-STATUS.md" + "version": "2.8.8", + "build": 54, + "release": "https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg", + "appArchive": "https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip", + "evidence": "https://github.com/AffPapa/neclip/blob/main/docs/RELEASE-2.8.8-STATUS.md" }, "done": [ "2.6.0: history actions, local search, trust status, screenshot QA, token preview, starter snippets and database backup/restore", @@ -21,7 +21,8 @@ "2.8.4: native menu Return routing, explicit privacy-aware snippet history, draft merge safety, file search decoding, streamed reads and cancellation-safe screenshot restart; synthetic-image Save dialog PNG/JPEG/cancel/reopen verified", "2.8.5: finish screenshot exports after the editor is hidden; reduce brace-heavy template scanning; independently verified signed and notarized public release", "2.8.6: defer older history rows until opened; publish project attribution on all public pages; sign/notarize and independently verify DMG and ZIP", - "2.8.7: respect native search focus and IME, reject queued images after opt-out, avoid redundant full-body normalization" + "2.8.7: respect native search focus and IME, reject queued images after opt-out, avoid redundant full-body normalization", + "2.8.8: native token insertion and document-scoped undo, lazy clipboard expansion, correct screenshot dimensions and guarded clipboard rollback" ], "inDevelopment": [], "next": [ diff --git a/docs/changelog.json b/docs/changelog.json index 94dae57..718086b 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -1,9 +1,23 @@ { "versions": [ + { + "version": "2.8.8", + "build": 54, + "status": "released", + "date": "2026-09-27", + "changes": [ + "Insert snippet tokens at the native caret or selection with document-scoped undo.", + "Read clipboard text for template expansion only when needed, on the data queue, with generation checks.", + "Use exported screenshot dimensions in history and preserve the prior clipboard on failed screenshot writes.", + "Label history filters and snippet preview input for accessibility.", + "380 XCTest (8 opt-in skips), 4 Swift Testing checks, strict Swift 6, ASan, TSan, required CI, CodeQL and Secret Scan passed.", + "Signed, notarized and stapled DMG and ZIP independently downloaded and verified." + ] + }, { "version": "2.8.7", "build": 53, - "status": "released", + "status": "superseded", "date": "2026-09-26", "changes": [ "Preserve filter/button, VoiceOver and IME keyboard handling in history search.", @@ -13,18 +27,6 @@ "Developer ID signing, notarization, stapling, Gatekeeper and independent public DMG/ZIP byte verification passed." ] }, - { - "version": "2.8.8", - "build": 54, - "status": "candidate", - "date": "2026-09-27", - "changes": [ - "Insert snippet tokens at the native caret or selection with document-scoped undo.", - "Read clipboard text for template expansion only when needed, on the data queue, with generation checks.", - "Use exported screenshot dimensions in history and preserve the prior clipboard on failed screenshot writes.", - "Label history filters and snippet preview input for accessibility." - ] - }, { "version": "2.8.6", "build": 52, diff --git a/docs/index.html b/docs/index.html index 9ce9e8b..8b0455c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -15,7 +15,7 @@ - + @@ -26,17 +26,17 @@
- 2.8.7 · выпущена + 2.8.8 · выпущена

Менеджер буфера обмена для Mac: история и сниппеты.

Нативная история буфера и готовые сниппеты прямо в строке меню Mac. Быстро, локально и без лишней системы вокруг.

- Скачать NeClip 2.8.7 - Скачать ZIP-архив + Скачать NeClip 2.8.8 + Скачать ZIP-архив Посмотреть исходники

macOS 14+ · Apple Silicon · MIT · Developer ID · нотарифицировано Apple · аккаунт не нужен

Откройте DMG или распакуйте ZIP, перетащите NeClip в «Программы» и запустите оттуда. Значок появится в строке меню, не в Dock.

-

История и сниппеты хранятся локально; поиск работает только на этом Mac, без облака и телеметрии. Проверки выпуска.

+

История и сниппеты хранятся локально; поиск работает только на этом Mac, без облака и телеметрии. Проверки выпуска.

@@ -77,12 +77,12 @@

Приватность видна, а не спрятана

Единственный поддерживаемый выпуск

-

NeClip 2.8.7 / build 53 — текущая подписанная и нотарифицированная версия.

-
2.8.7
выпущена · 26 сентября 2026
Точнее работа клавиатуры и настроек приватности.
  • Поиск сохраняет нативную работу фильтров, кнопок, модифицированных клавиш и IME
  • Отключение истории изображений учитывается перед записью скриншота из очереди
  • При совпадении заголовка поиск пропускает лишнее преобразование большого текста

371 XCTest (8 ожидаемых пропусков) и 4 Swift Testing прошли; strict Swift 6, ASan, TSan, обязательные CI, CodeQL и secret scan прошли. DMG и ZIP подписаны, нотарифицированы и приняты Gatekeeper.

Проверки выпуска и ограничения
-

DMG: 2 142 658 байт · SHA-256: 3111009d22c8ad9e56a3951f215c63cfe669786ff0f8596ca8d80ed007e8984a

-

ZIP: 2 109 242 байт · скачать архив · SHA-256: a7ff40101da09d16c69ac2c5f8535bd1b547a3af84308b823012c3bd8e8f169d

-

DMG и ZIP опубликованы вместе в неизменяемом релизе v2.8.7.

-

Исходный коммит: 2cad4383bcc6d1a7c8c28c6e60bbfe67a3b11be6

+

NeClip 2.8.8 / build 54 — текущая подписанная и нотарифицированная версия.

+
2.8.8
выпущена · 27 сентября 2026
Точнее редактирование сниппетов и копирование снимков.
  • Токен заменяет выделение или вставляется у курсора; Undo/Redo отделены для каждого сниппета
  • История показывает размеры готового скриншота с фоном
  • При ошибке записи снимка прежний буфер восстанавливается, если его не изменило другое приложение
  • Подстановка обычного текста не читает буфер заранее

380 XCTest (8 ожидаемых пропусков) и 4 Swift Testing прошли; strict Swift 6, ASan, TSan, обязательные CI, CodeQL и secret scan прошли. DMG и ZIP подписаны, нотарифицированы и приняты Gatekeeper.

Проверки выпуска и ограничения
+

DMG: 2 146 754 байт · SHA-256: 6bf16a132261d66926e7b327e835dd0ee526e477d19d8907b207a7f2ea164d74

+

ZIP: 2 111 918 байт · скачать архив · SHA-256: e33fe900103f5999928054f4de8535ab74d8f1ceab53df6fbaf130327c12ae38

+

DMG и ZIP опубликованы вместе в неизменяемом релизе v2.8.8.

+

Исходный коммит: 76a3884810539a5e8bdb226624e5c46e05dcc376

diff --git a/docs/project.json b/docs/project.json index 39d8372..a4d35d1 100644 --- a/docs/project.json +++ b/docs/project.json @@ -20,9 +20,10 @@ "sourceCandidate": { "version": "2.8.8", "build": 54, - "status": "candidate", - "publicVersion": "2.8.7", - "scope": "native snippet token selection and undo, guarded lazy clipboard expansion, screenshot history dimensions and failed-write preservation" + "status": "released", + "publicVersion": "2.8.8", + "scope": "native snippet token selection and undo, guarded lazy clipboard expansion, screenshot history dimensions and failed-write preservation", + "sourceCommit": "76a3884810539a5e8bdb226624e5c46e05dcc376" }, "canonical": { "repository": "https://github.com/AffPapa/neclip", @@ -32,8 +33,8 @@ "releaseFeed": "https://github.com/AffPapa/neclip/releases.atom", "changelog": "https://github.com/AffPapa/neclip/blob/main/CHANGELOG.md", "backlog": "https://github.com/AffPapa/neclip/blob/main/BACKLOG.md", - "engineeringAudit": "https://github.com/AffPapa/neclip/blob/main/docs/RELEASE-2.8.7-STATUS.md", - "releaseEvidence": "https://github.com/AffPapa/neclip/blob/main/docs/RELEASE-2.8.7-STATUS.md", + "engineeringAudit": "https://github.com/AffPapa/neclip/blob/main/docs/RELEASE-2.8.8-STATUS.md", + "releaseEvidence": "https://github.com/AffPapa/neclip/blob/main/docs/RELEASE-2.8.8-STATUS.md", "projectMap": "https://github.com/AffPapa/neclip/blob/main/docs/PROJECT-MAP.md", "security": "https://github.com/AffPapa/neclip/blob/main/SECURITY.md", "license": "https://github.com/AffPapa/neclip/blob/main/LICENSE" @@ -115,5 +116,5 @@ "a permanent Dock icon", "tags or smart collections" ], - "featuresVersion": "2.8.7 released; public DMG and ZIP artifacts are identified by version.json" + "featuresVersion": "2.8.8 released; public DMG and ZIP artifacts are identified by version.json" } diff --git a/docs/version.json b/docs/version.json index c459687..9254c53 100644 --- a/docs/version.json +++ b/docs/version.json @@ -1,22 +1,22 @@ { - "version": "2.8.7", - "build": 53, + "version": "2.8.8", + "build": 54, "status": "released", "platform": "macOS 14+ on Apple Silicon", - "release": "https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.dmg", - "releasePage": "https://github.com/AffPapa/neclip/releases/tag/v2.8.7", - "checksum": "https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.dmg.sha256", - "sha256": "3111009d22c8ad9e56a3951f215c63cfe669786ff0f8596ca8d80ed007e8984a", - "sizeBytes": 2142658, - "publishedAt": "2026-09-26T15:55:01Z", + "release": "https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg", + "releasePage": "https://github.com/AffPapa/neclip/releases/tag/v2.8.8", + "checksum": "https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.dmg.sha256", + "sha256": "6bf16a132261d66926e7b327e835dd0ee526e477d19d8907b207a7f2ea164d74", + "sizeBytes": 2146754, + "publishedAt": "2026-09-27T11:11:38Z", "source": "https://github.com/AffPapa/neclip", - "sourceCommit": "2cad4383bcc6d1a7c8c28c6e60bbfe67a3b11be6", + "sourceCommit": "76a3884810539a5e8bdb226624e5c46e05dcc376", "appArchive": { - "release": "https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip", - "releasePage": "https://github.com/AffPapa/neclip/releases/tag/v2.8.7", - "checksum": "https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip.sha256", - "sha256": "a7ff40101da09d16c69ac2c5f8535bd1b547a3af84308b823012c3bd8e8f169d", - "sizeBytes": 2109242, - "publishedAt": "2026-09-26T15:55:01Z" + "release": "https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip", + "releasePage": "https://github.com/AffPapa/neclip/releases/tag/v2.8.8", + "checksum": "https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip.sha256", + "sha256": "e33fe900103f5999928054f4de8535ab74d8f1ceab53df6fbaf130327c12ae38", + "sizeBytes": 2111918, + "publishedAt": "2026-09-27T11:11:38Z" } } diff --git a/scripts/test-site.rb b/scripts/test-site.rb index 073f54c..abcfc82 100644 --- a/scripts/test-site.rb +++ b/scripts/test-site.rb @@ -53,15 +53,15 @@ def test_rejects_redirected_checksum_link def test_current_release_archive_metadata_matches_public_pages version = JSON.parse(File.read(File.join(@fixture, 'docs/version.json'))) archive = version.fetch('appArchive') - assert_equal 'https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip', archive.fetch('release') - assert_equal 'https://github.com/AffPapa/neclip/releases/tag/v2.8.7', archive.fetch('releasePage') - assert_equal 'https://github.com/AffPapa/neclip/releases/download/v2.8.7/NeClip-2.8.7.zip.sha256', archive.fetch('checksum') - assert_equal 'a7ff40101da09d16c69ac2c5f8535bd1b547a3af84308b823012c3bd8e8f169d', archive.fetch('sha256') - assert_equal 2_109_242, archive.fetch('sizeBytes') + assert_equal 'https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip', archive.fetch('release') + assert_equal 'https://github.com/AffPapa/neclip/releases/tag/v2.8.8', archive.fetch('releasePage') + assert_equal 'https://github.com/AffPapa/neclip/releases/download/v2.8.8/NeClip-2.8.8.zip.sha256', archive.fetch('checksum') + assert_equal 'e33fe900103f5999928054f4de8535ab74d8f1ceab53df6fbaf130327c12ae38', archive.fetch('sha256') + assert_equal 2_111_918, archive.fetch('sizeBytes') html = File.read(File.join(@fixture, 'docs/index.html')) readme = File.read(File.join(@fixture, 'README.md')) - assert_includes html, 'releases/download/v2.8.7/NeClip-2.8.7.zip' - assert_includes readme, 'releases/download/v2.8.7/NeClip-2.8.7.zip' + assert_includes html, 'releases/download/v2.8.8/NeClip-2.8.8.zip' + assert_includes readme, 'releases/download/v2.8.8/NeClip-2.8.8.zip' end def test_rejects_wrong_archive_url @@ -93,8 +93,8 @@ def test_rejects_stale_archive_size end def test_rejects_missing_archive_checksum_in_evidence - evidence = File.join(@fixture, 'docs', 'RELEASE-2.8.7-STATUS.md') - File.write(evidence, File.read(evidence).sub('a7ff40101da09d16c69ac2c5f8535bd1b547a3af84308b823012c3bd8e8f169d', '')) + evidence = File.join(@fixture, 'docs', 'RELEASE-2.8.8-STATUS.md') + File.write(evidence, File.read(evidence).sub('e33fe900103f5999928054f4de8535ab74d8f1ceab53df6fbaf130327c12ae38', '')) _, err, status = verify refute status.success? assert_includes err, 'archive evidence mismatch'