From e0fda218e61f233c6b7a36e8a542792136e0be56 Mon Sep 17 00:00:00 2001 From: OnlineFix Date: Fri, 11 Sep 2026 10:44:09 +0100 Subject: [PATCH] [verified] chore: prepare v1.2.1 unsigned beta --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .../ISSUE_TEMPLATE/compatibility_report.yml | 102 ++++++++++++++++++ .github/workflows/build-sign-release.yml | 2 +- README.md | 11 +- build.bat | 4 +- pearipherals_version.py | 2 +- tests/test_core.py | 8 +- tests/test_release.py | 65 +++++++++++ 8 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/compatibility_report.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f1d8a05..81d48bd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -16,7 +16,7 @@ body: attributes: label: Pearipherals version description: Use the About/version information, release name or source commit. State whether the EXE is an unsigned prerelease. - placeholder: "1.2.0 unsigned prerelease" + placeholder: "Current unsigned prerelease" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/compatibility_report.yml b/.github/ISSUE_TEMPLATE/compatibility_report.yml new file mode 100644 index 0000000..ebd42b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/compatibility_report.yml @@ -0,0 +1,102 @@ +name: Compatibility report +description: Tell us whether Pearipherals works on your Apple hardware and Windows setup +title: "[Compatibility]: " +labels: + - compatibility +body: + - type: markdown + attributes: + value: | + Working and failed setups are both useful. Please report product-level details only. + + **Privacy:** do not include a serial number, Bluetooth address, HID identifier, username, full local path, registry export, configuration file, error log, or credential. Do not attach raw HID data unless a maintainer has explicitly arranged a private collection process. + + Do not disable Windows security protections to test Pearipherals. + + - type: input + id: pearipherals-version + attributes: + label: Pearipherals version + description: Copy the version from About / status or the GitHub release name. + placeholder: "1.2.1 unsigned prerelease" + validations: + required: true + + - type: input + id: windows-version + attributes: + label: Windows version + description: Include Windows 10/11, release, and OS build. Do not include your device name. + placeholder: "Windows 11 24H2, OS build 26100.x" + validations: + required: true + + - type: input + id: device-model + attributes: + label: Apple device model + description: List the product family and visible model revision only. Do not include any serial number or device identifier. + placeholder: "Magic Keyboard A2450 and Magic Trackpad 2 A1535" + validations: + required: true + + - type: dropdown + id: connection + attributes: + label: Connection + options: + - Bluetooth + - USB data + - USB-C to DisplayPort video-only + - Mixed connections + - Other or unknown + validations: + required: true + + - type: dropdown + id: overall-result + attributes: + label: Overall result + options: + - Works fully + - Works partly + - Does not work + validations: + required: true + + - type: checkboxes + id: features-tested + attributes: + label: Features tested + description: Select every feature you actually tried. + options: + - label: Magic Keyboard F-row controls + - label: F6 region-selection overlay + - label: Magic Trackpad pointer and two-finger scrolling + - label: Three-finger swipes + - label: Three-finger drag + - label: Natural scrolling toggle + - label: Keyboard or trackpad battery display + - label: Display brightness + - label: Autostart, quit, and relaunch + + - type: textarea + id: notes + attributes: + label: What worked or failed? + description: Keep this short. For a failure, give the smallest repeatable sequence and remove private information. + placeholder: "F-row, battery, and two-finger scrolling work. Three-finger swipe left does not open Task View after reconnecting Bluetooth." + validations: + required: true + + - type: checkboxes + id: privacy-confirmation + attributes: + label: Privacy and safety confirmation + options: + - label: I removed every serial number, Bluetooth address, device identifier, username, full local path, registry value, and credential. + required: true + - label: I did not attach raw HID data, a full configuration, an error log, a registry export, or a crash dump. + required: true + - label: I did not disable or recommend disabling Windows security protections. + required: true \ No newline at end of file diff --git a/.github/workflows/build-sign-release.yml b/.github/workflows/build-sign-release.yml index 8e1ea08..1a30c14 100644 --- a/.github/workflows/build-sign-release.yml +++ b/.github/workflows/build-sign-release.yml @@ -6,7 +6,7 @@ on: version: description: Release version (MAJOR.MINOR.PATCH) required: true - default: 1.2.0 + default: 1.2.1 submit_for_signing: description: Submit this manual build to SignPath required: true diff --git a/README.md b/README.md index f80622f..6b0bded 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,15 @@ trackpad driver. Pearipherals is portable. Its configuration and optional error log sit beside the EXE as `pearipherals.json` and `pearipherals.err.log`. +## Help test Pearipherals + +Pearipherals needs reports from Apple hardware beyond the maintainer's setup. +If you try the beta, submit a short [compatibility report](../../issues/new?template=compatibility_report.yml), +including when everything works. A working setup is as useful as a failed setup +because it tells us which device revisions and connection paths are reliable. +The form asks only for product-level details and warns against sharing serial +numbers, Bluetooth addresses, raw HID data, or other private information. + ## Windows security, unsigned builds, and Smart App Control An **unsigned** prerelease has no Authenticode signature, so Windows cannot @@ -201,7 +210,7 @@ Pearipherals first managed them. ```bat python -m venv .venv .venv\Scripts\pip install -r requirements.txt -set PEARIPHERALS_VERSION=1.2.0 +set PEARIPHERALS_VERSION=1.2.1 build.bat ``` diff --git a/build.bat b/build.bat index 9b981d7..8a3129c 100644 --- a/build.bat +++ b/build.bat @@ -1,7 +1,7 @@ @echo off rem Build Pearipherals.exe into dist\ — run from the repo root. -rem Set PEARIPHERALS_VERSION for a release build; local builds default to 1.2.0. -if not defined PEARIPHERALS_VERSION set "PEARIPHERALS_VERSION=1.2.0" +rem Set PEARIPHERALS_VERSION for a release build; local builds default to 1.2.1. +if not defined PEARIPHERALS_VERSION set "PEARIPHERALS_VERSION=1.2.1" if not exist .venv ( python -m venv .venv || goto :err .venv\Scripts\pip install -r requirements.txt || goto :err diff --git a/pearipherals_version.py b/pearipherals_version.py index 4c55fba..dab95e9 100644 --- a/pearipherals_version.py +++ b/pearipherals_version.py @@ -5,7 +5,7 @@ from dataclasses import dataclass -APP_VERSION = "1.2.0" +APP_VERSION = "1.2.1" # A build identity is always prefixed so the reader knows exactly what was # measured. `git:` names the exact source revision a frozen build was produced diff --git a/tests/test_core.py b/tests/test_core.py index 62850c1..e324134 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3474,8 +3474,10 @@ def test_invalid_embedded_manifest_never_falls_back_to_adjacent_manifest(self): identity = self._identity(exe_dir, meipass=meipass)() + from pearipherals_version import APP_VERSION + self.assertEqual("git:" + "0" * 40, identity.build_id) - self.assertEqual("1.2.0", identity.version) + self.assertEqual(APP_VERSION, identity.version) def test_present_falsey_meipass_never_reads_adjacent_or_working_directory(self): with tempfile.TemporaryDirectory() as exe_dir, \ @@ -3484,13 +3486,15 @@ def test_present_falsey_meipass_never_reads_adjacent_or_working_directory(self): self._write_manifest(working_dir, "git:" + "f" * 40) previous_directory = os.getcwd() try: + from pearipherals_version import APP_VERSION + os.chdir(working_dir) root_relative = os.path.splitdrive(working_dir)[1] for meipass in ("", root_relative): with self.subTest(meipass=meipass): identity = self._identity(exe_dir, meipass=meipass)() self.assertEqual("git:" + "0" * 40, identity.build_id) - self.assertEqual("1.2.0", identity.version) + self.assertEqual(APP_VERSION, identity.version) finally: os.chdir(previous_directory) diff --git a/tests/test_release.py b/tests/test_release.py index 0eff3f3..fc783d8 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -26,6 +26,8 @@ GITIGNORE = ROOT / ".gitignore" PRIVACY_POLICY = ROOT / "PRIVACY.md" README = ROOT / "README.md" +COMPATIBILITY_REPORT_FORM = ISSUE_TEMPLATE_DIR / "compatibility_report.yml" +TARGET_RELEASE_VERSION = "1.2.1" SPEC = importlib.util.spec_from_file_location("write_version_info", SCRIPT) MODULE = importlib.util.module_from_spec(SPEC) assert SPEC.loader is not None @@ -62,6 +64,25 @@ def test_cli_writes_utf8_version_resource(self): ) self.assertIn("ProductVersion', '1.2.3'", output.read_text(encoding="utf-8")) + def test_runtime_declares_target_release_version(self): + import pearipherals_version + + self.assertEqual(TARGET_RELEASE_VERSION, pearipherals_version.APP_VERSION) + + def test_local_and_hosted_build_defaults_match_runtime_version(self): + import pearipherals_version + + build_script = BUILD_SCRIPT.read_text(encoding="utf-8") + workflow = WORKFLOW.read_text(encoding="utf-8") + self.assertIn( + f'if not defined PEARIPHERALS_VERSION set "PEARIPHERALS_VERSION={pearipherals_version.APP_VERSION}"', + build_script, + ) + self.assertRegex( + workflow, + rf"(?m)^\s*default:\s*{re.escape(pearipherals_version.APP_VERSION)}\s*$", + ) + def test_release_workflow_pins_actions_and_avoids_template_injection(self): workflow = WORKFLOW.read_text(encoding="utf-8") uses = re.findall(r"^\s*uses:\s*([^\s#]+)", workflow, flags=re.MULTILINE) @@ -289,6 +310,12 @@ def test_bug_form_collects_compatibility_context_without_private_data(self): with self.subTest(warning=warning): self.assertIn(warning, form) + def test_bug_form_uses_a_version_neutral_release_example(self): + form = BUG_REPORT_FORM.read_text(encoding="utf-8").lower() + + self.assertIn('placeholder: "current unsigned prerelease"', form) + self.assertNotIn("1.2.0 unsigned prerelease", form) + def test_feature_form_captures_problem_scope_alternatives_and_safety(self): self.assertTrue( FEATURE_REQUEST_FORM.is_file(), @@ -310,6 +337,33 @@ def test_feature_form_captures_problem_scope_alternatives_and_safety(self): self.assertIn("privacy", form) self.assertIn("remove sensitive information", form) + def test_compatibility_form_collects_success_and_failure_reports_safely(self): + self.assertTrue( + COMPATIBILITY_REPORT_FORM.is_file(), + f"missing compatibility form: {COMPATIBILITY_REPORT_FORM}", + ) + form = COMPATIBILITY_REPORT_FORM.read_text(encoding="utf-8").lower() + for field_id in ( + "pearipherals-version", + "windows-version", + "device-model", + "connection", + "overall-result", + "features-tested", + "privacy-confirmation", + ): + with self.subTest(field_id=field_id): + self.assertIn(f"id: {field_id}", form) + for result in ("works fully", "works partly", "does not work"): + self.assertIn(result, form) + for warning in ( + "serial number", + "bluetooth address", + "raw hid", + "do not disable windows security", + ): + self.assertIn(warning, form) + def test_issue_chooser_disables_blank_issues_and_links_private_security(self): self.assertTrue(ISSUE_CONFIG.is_file(), f"missing issue config: {ISSUE_CONFIG}") config = ISSUE_CONFIG.read_text(encoding="utf-8").lower() @@ -344,6 +398,17 @@ def test_dependabot_updates_only_github_actions_on_a_conservative_schedule(self) class SupportDocumentationTests(unittest.TestCase): + def test_readme_invites_beta_compatibility_reports(self): + readme = README.read_text(encoding="utf-8") + + self.assertIn("## Help test Pearipherals", readme) + self.assertIn( + "../../issues/new?template=compatibility_report.yml", + readme, + ) + self.assertIn("working setup", readme.lower()) + self.assertIn("failed setup", readme.lower()) + def test_readme_documents_the_complete_support_and_lifecycle_surface(self): readme = README.read_text(encoding="utf-8") lowered = readme.lower()