diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8383631..c2d53ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,36 @@ jobs: - name: Run verification suite run: npm run verify + node20: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up lowest supported Node.js + uses: actions/setup-node@v6 + with: + node-version: '20.19.0' + cache: 'npm' + cache-dependency-path: | + package-lock.json + server/package-lock.json + + - name: Install root dependencies + run: npm ci + + - name: Install server dependencies + run: npm ci + working-directory: server + + - name: Run unit and relay integration gates + run: | + npm run test:coverage + node scripts/test-server-routes.mjs + node scripts/test-server-ws.mjs + env: + ADMIN_METRICS_TOKEN: verify-admin-token-with-more-than-32-chars + e2e: # Kept separate from `verify`: this job needs a downloaded browser, so a # failure here should read as "the browser flow broke", not as a broken @@ -66,12 +96,27 @@ jobs: - name: Install root dependencies run: npm ci - - name: Install Playwright Chromium - run: npx playwright install --with-deps chromium chromium-headless-shell + - name: Install server dependencies + run: npm ci + working-directory: server + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium chromium-headless-shell firefox webkit # The extension specs load dist/chrome, so the artifact has to exist. - name: Build the extension run: npm run build:extension - - name: Run extension E2E smoke tests + - name: Run cross-browser detection and extension E2E tests run: npm run test:e2e + + - name: Upload browser failure diagnostics + if: failure() + uses: actions/upload-artifact@v7 + with: + name: e2e-failure-diagnostics + path: | + test-results/ + playwright-report/ + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/race-tests.yml b/.github/workflows/race-tests.yml new file mode 100644 index 0000000..8f14849 --- /dev/null +++ b/.github/workflows/race-tests.yml @@ -0,0 +1,55 @@ +name: Repeated Race Tests + +on: + workflow_dispatch: + schedule: + - cron: '17 3 * * *' + +permissions: + contents: read + +concurrency: + group: race-tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + extension-races: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Install server dependencies + run: npm ci + working-directory: server + + - name: Install Playwright Chromium + run: npx playwright install --with-deps chromium chromium-headless-shell + + - name: Build the extension + run: npm run build:extension + + - name: Repeat race-sensitive extension tests + run: npm run test:e2e:race + + - name: Upload failure diagnostics + if: failure() + uses: actions/upload-artifact@v7 + with: + name: race-test-diagnostics + path: | + test-results/ + playwright-report/ + if-no-files-found: ignore + retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7014be..3422939 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,23 +5,219 @@ on: tags: - 'v*' -# A release run must never be interrupted (it commits back to main and publishes -# artifacts). Only dedupe accidental re-pushes of the same tag. concurrency: group: release-${{ github.ref_name }} cancel-in-progress: false jobs: - release-server: + preflight: runs-on: ubuntu-latest + permissions: + contents: read + checks: read + outputs: + version: ${{ steps.release-ref.outputs.version }} + tag-commit: ${{ steps.release-ref.outputs.tag_commit }} + release-timestamp: ${{ steps.release-ref.outputs.release_timestamp }} + steps: + - name: Checkout release tag + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: | + package-lock.json + server/package-lock.json + + - name: Validate annotated tag, main commit, and required checks + id: release-ref + run: node scripts/release-preflight.mjs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + prepare-release: + needs: preflight + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + prepared-commit: ${{ steps.version-commit.outputs.prepared_commit }} + steps: + - name: Checkout release tag + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + + - name: Prepare and validate every release-version source + env: + VERSION: ${{ needs.preflight.outputs.version }} + RELEASE_TIMESTAMP: ${{ needs.preflight.outputs.release-timestamp }} + run: | + node scripts/prepare-release.mjs "$VERSION" "$RELEASE_TIMESTAMP" + node scripts/release-preflight.mjs --sources "$VERSION" + + - name: Commit and push prepared release to main + id: version-commit + env: + VERSION: ${{ needs.preflight.outputs.version }} + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add extension/manifest.base.json shared/constants.js package.json package-lock.json website/version.json website/template.html website/llms.txt README.md + if git diff --cached --quiet; then + echo "Release versions already match v$VERSION" + else + git commit -m "chore(release): update versions to v$VERSION [skip ci]" + fi + git push origin HEAD:main + echo "prepared_commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + verify-prepared-release: + needs: [preflight, prepare-release] + runs-on: ubuntu-latest + steps: + - name: Checkout prepared release commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.prepare-release.outputs.prepared-commit }} + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: | + package-lock.json + server/package-lock.json + + - name: Validate every prepared version source + env: + VERSION: ${{ needs.preflight.outputs.version }} + run: node scripts/release-preflight.mjs --sources "$VERSION" + + - name: Install root dependencies + run: npm ci + + - name: Install server dependencies + run: npm ci + working-directory: server + + - name: Run complete release verification + run: npm run verify + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium chromium-headless-shell firefox webkit + + - name: Run browser E2E suite + run: npm run test:e2e + + - name: Build relay container without publishing + run: docker build --file server/Dockerfile --tag koalasync-release-preflight . + + - name: Smoke-test relay container + run: | + CONTAINER_ID=$(docker run --detach --publish 127.0.0.1::3000 --env SERVER_SALT=release-preflight-salt-with-more-than-thirty-two-chars koalasync-release-preflight) + trap 'docker rm --force "$CONTAINER_ID" >/dev/null 2>&1 || true' EXIT + HOST_PORT=$(docker port "$CONTAINER_ID" 3000/tcp | sed 's/.*://') + for attempt in $(seq 1 30); do + if curl --fail --silent "http://127.0.0.1:$HOST_PORT/health" >/dev/null; then + exit 0 + fi + sleep 1 + done + docker logs "$CONTAINER_ID" + exit 1 + + release-extension-draft: + needs: [preflight, prepare-release, verify-prepared-release] + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Checkout prepared release commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.prepare-release.outputs.prepared-commit }} + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + + - name: Install dependencies and build release artifacts + run: | + npm ci + npm run build:extension + node website/build.cjs + + - name: Validate Firefox package + run: npx addons-linter --warnings-as-errors dist/koalasync-firefox.zip + + - name: Generate extension checksums + working-directory: dist + run: sha256sum koalasync-chrome.zip koalasync-firefox.zip > SHA256SUMS + + - name: Validate release assets before publication + run: node scripts/verify-published-release.mjs "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --asset-dir dist --skip-attestation + + - name: Attest extension archives + uses: actions/attest@v4 + with: + subject-path: dist/koalasync-*.zip + + - name: Create draft GitHub release + uses: softprops/action-gh-release@v3 + with: + files: | + dist/koalasync-chrome.zip + dist/koalasync-firefox.zip + dist/SHA256SUMS + name: Release ${{ github.ref_name }} + generate_release_notes: true + draft: true + prerelease: false + + - name: Verify draft extension release + run: node scripts/verify-published-release.mjs "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload website artifacts + uses: actions/upload-artifact@v7 + with: + name: website-www + path: website/www/ + if-no-files-found: error + + release-server: + needs: [preflight, prepare-release, verify-prepared-release, release-extension-draft] + runs-on: ubuntu-latest + env: + IMAGE: ghcr.io/shik3i/koalasync permissions: contents: read packages: write id-token: write attestations: write steps: - - name: Checkout code + - name: Checkout prepared release commit uses: actions/checkout@v7 + with: + ref: ${{ needs.prepare-release.outputs.prepared-commit }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -37,7 +233,7 @@ jobs: id: meta uses: docker/metadata-action@v6 with: - images: ghcr.io/${{ github.repository }} + images: ${{ env.IMAGE }} tags: | type=raw,value=latest type=ref,event=tag @@ -52,126 +248,45 @@ jobs: platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - # Reuse layers across releases to speed up the multi-arch build. cache-from: type=gha cache-to: type=gha,mode=max - - name: Generate artifact attestation + - name: Attest relay image uses: actions/attest@v4 with: - subject-name: ghcr.io/${{ github.repository }} + subject-name: ${{ env.IMAGE }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true - release-extension: + - name: Verify manifest, provenance, and running image + env: + DIGEST: ${{ steps.build.outputs.digest }} + SOURCE_DIGEST: ${{ needs.preflight.outputs.tag-commit }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + docker buildx imagetools inspect "$IMAGE@$DIGEST" --raw > /tmp/koalasync-manifest.json + node -e 'const m=require("/tmp/koalasync-manifest.json"); const p=new Set(m.manifests.map(x=>`${x.platform.os}/${x.platform.architecture}`)); for (const x of ["linux/amd64","linux/arm64"]) if(!p.has(x)) throw new Error(`missing platform ${x}`)' + gh attestation verify "oci://$IMAGE@$DIGEST" --repo "$GITHUB_REPOSITORY" --signer-workflow "$GITHUB_REPOSITORY/.github/workflows/release.yml" --source-ref "$GITHUB_REF" --source-digest "$SOURCE_DIGEST" --deny-self-hosted-runners + docker pull --platform linux/amd64 "$IMAGE@$DIGEST" + CONTAINER_ID=$(docker run --detach --publish 127.0.0.1::3000 --env SERVER_SALT=release-smoke-salt-with-more-than-thirty-two-chars "$IMAGE@$DIGEST") + trap 'docker rm --force "$CONTAINER_ID" >/dev/null 2>&1 || true' EXIT + HOST_PORT=$(docker port "$CONTAINER_ID" 3000/tcp | sed 's/.*://') + for attempt in $(seq 1 30); do + if curl --fail --silent "http://127.0.0.1:$HOST_PORT/health" >/dev/null; then + exit 0 + fi + sleep 1 + done + docker logs "$CONTAINER_ID" + exit 1 + + finalize-release: + needs: [prepare-release, verify-prepared-release, release-extension-draft, release-server] runs-on: ubuntu-latest permissions: contents: write - id-token: write - attestations: write steps: - - name: Checkout code - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '24' - cache: 'npm' - - - name: Extract version from tag - id: version - run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT - - - name: Inject version into source files - run: | - VERSION=${{ steps.version.outputs.VERSION }} - DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - echo "Injecting version $VERSION from tag $GITHUB_REF_NAME..." - - # 1. extension/manifest.base.json - jq --arg v "$VERSION" '.version = $v' extension/manifest.base.json > tmp.json && mv tmp.json extension/manifest.base.json - echo " ✓ manifest.base.json -> $VERSION" - - # 2. shared/constants.js — APP_VERSION - sed -i "s/export const APP_VERSION = [\"'].*[\"']/export const APP_VERSION = \"$VERSION\"/" shared/constants.js - echo " ✓ shared/constants.js -> $VERSION" - - # 3. package.json - jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json - echo " ✓ package.json -> $VERSION" - - # 4. package-lock.json root package metadata - jq --arg v "$VERSION" '.version = $v | .packages[""].version = $v' package-lock.json > tmp.json && mv tmp.json package-lock.json - echo " ✓ package-lock.json -> $VERSION" - - # 5. website/version.json - jq -n --arg v "$VERSION" --arg d "$DATE" '{version: $v, date: $d}' > website/version.json - echo " ✓ website/version.json -> version $VERSION, date $DATE" - - # 6. website/template.html — SoftwareApplication schema - sed -i "s/\"softwareVersion\": \".*\"/\"softwareVersion\": \"$VERSION\"/" website/template.html - echo " ✓ website/template.html -> softwareVersion $VERSION" - - # 7. website/llms.txt — machine-readable release metadata - sed -i "s/Current website release: .*/Current website release: $VERSION/" website/llms.txt - echo " ✓ website/llms.txt -> $VERSION" - - # 8. README.md — version badge & banner - sed -i "s|Release-v[0-9]\+\.[0-9]\+\.[0-9]\+-blue|Release-v$VERSION-blue|g" README.md - sed -i "s/New v[0-9]\+\.[0-9]\+\.[0-9]\+ Release/New v$VERSION Release/g" README.md - echo " ✓ README.md -> v$VERSION" - - echo "Version injection complete." - - - name: Commit and push version updates back to main - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add extension/manifest.base.json shared/constants.js package.json package-lock.json website/version.json website/template.html website/llms.txt README.md - git commit -m "chore(release): update versions to $GITHUB_REF_NAME [skip ci]" || echo "No changes to commit" - git push origin HEAD:main + - name: Publish verified GitHub release + run: gh release edit "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft=false --verify-tag env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Build Extensions - run: | - npm ci - npm run build:extension - - - name: Generate extension checksums - run: | - cd dist - sha256sum koalasync-chrome.zip koalasync-firefox.zip > SHA256SUMS - cat SHA256SUMS - - - name: Generate artifact attestation for extensions - uses: actions/attest@v4 - with: - subject-path: dist/koalasync-*.zip - - - name: Build Website - run: node website/build.cjs - - - name: Upload Website Artifacts - uses: actions/upload-artifact@v7 - with: - name: website-www - path: website/www/ - if-no-files-found: error - - - name: Create GitHub Release - uses: softprops/action-gh-release@v3 - with: - files: | - dist/koalasync-chrome.zip - dist/koalasync-firefox.zip - dist/SHA256SUMS - name: Release ${{ github.ref_name }} - generate_release_notes: true - draft: false - prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..892bbeb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,38 @@ +# Repository agent release rules + +These rules are mandatory for every automated agent working in this repository. + +## Before changing code + +- Run `git pull --ff-only` before starting. +- Inspect branch, remote tracking, dirty state, and relevant release workflow. +- Preserve unrelated work and stage only reviewed paths. + +## Release verification + +- Never treat a host macOS browser run as GitHub Linux parity. +- Run checks proportionate to the changed files. Markdown-only changes do not + require browser E2E or `release:gate`. +- `npm run release:gate -- MAJOR.MINOR.PATCH [--candidate]` remains available + when release code itself needs Linux/AMD64 parity validation. It prepares the + requested version only inside its isolated clone. +- All OCI image references must use the canonical lowercase + `ghcr.io/shik3i/koalasync`; never construct Docker references from the + case-preserving `${{ github.repository }}` value. The local gate enforces this. + +## Before pushing a release tag + +- Fast-forward local `main` and confirm a clean tree at exact `origin/main`. +- Wait for `verify`, `node20`, and `e2e` on that exact `main` commit. +- Do not edit release-version sources manually. The tag workflow owns the + atomic version update. +- Create an annotated exact SemVer tag such as `v3.1.6`. +- Confirm tag target equals `origin/main`, then push the tag once. +- The workflow must extract the version, update and validate every version + source, commit `chore(release): update versions to vX.Y.Z [skip ci]`, and push + that commit directly to `main` before building release outputs. +- Chrome, Firefox, website, and relay outputs must use the exact prepared commit. +- Monitor the complete release workflow. Distinguish tag push, CI, draft assets, + container publication, attestations, and public GitHub Release status. +- Never call a release complete while any job is pending, failed, or skipped. +- Never reuse, move, or force-push a published tag. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abba016..588f60b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ Please note that by participating in this project, you agree to abide by our [Co ### Prerequisites -- **Node.js** v20.9+ +- **Node.js** v20.19+ - **Docker** (for local relay server testing) ### Quick Start @@ -96,7 +96,9 @@ KoalaSync uses a **single source of truth** for all protocol constants in `share ## Version Numbers > [!CAUTION] -> **Never manually bump version numbers.** The CI pipeline injects the version from the git tag into `manifest.base.json`, `shared/constants.js`, and `package.json` during release builds. Manual bumps cause conflicts. +> **Never edit release versions independently.** An annotated exact SemVer tag +> triggers the release workflow, which updates every version source atomically, +> validates the result, and pushes its generated version commit to `main`. --- diff --git a/README.md b/README.md index b78d08e..ea93620 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Release Status - GitHub release + GitHub release License Firefox Add-on Chrome Extension @@ -15,7 +15,7 @@

KoalaSync is a lightweight Browser Extension and Relay Server for synchronized video playback on almost any website with a video element—YouTube, Twitch, Netflix, Emby, Jellyfin, and beyond. Built with a focus on Data Sovereignty and Performance.

-

New v3.1.3 Release! — See what's changed

+

New v3.1.5 Release! — See what's changed

### 🌟 Why KoalaSync? diff --git a/docs/AI_INIT.md b/docs/AI_INIT.md index 7fddb36..c9bdbc6 100644 --- a/docs/AI_INIT.md +++ b/docs/AI_INIT.md @@ -118,17 +118,28 @@ Before starting any task, committing, or pushing, you **MUST** run `git pull --r > [!CAUTION] > **AI AGENTS MUST FOLLOW THIS EXACT SEQUENCE WHEN RELEASING A NEW VERSION OR TAGGING.** > -> **🚫 NO MANUAL VERSION BUMPING**: You MUST **NEVER** manually modify the version strings in `package.json`, `extension/manifest.base.json`, or `website/version.json`. The GitHub Actions CI pipeline automatically extracts the version from the git tag (e.g. `v2.0.5` -> `2.0.5`), injects it into all target files, and commits the updates back to `main` with `[skip ci]`. Manual bumps will cause merge conflicts and build failures. +> **🚫 NO INDEPENDENT VERSION EDITS**: Never edit only one version source. The +> annotated SemVer tag workflow extracts the version, updates every source, +> validates them, and pushes its generated `[skip ci]` version commit directly +> to `main`. > - **Website Versioning**: **NEVER** manually modify generated version strings in `website/www/`. The website build injects version data from `website/version.json` into generated output. 1. **MANDATORY SYNTAX & LINT CHECKS**: Before staging, committing, or pushing any changes, you **MUST** run both checks on every modified JavaScript file: - **Syntax Validation**: Run `node -c` on every single modified JavaScript file (e.g., `node -c extension/background.js` and `node -c extension/content.js`). **NEVER** commit or push code that fails this check. - **ESLint Validation**: Run `npm run lint` (or `npx eslint .`). The output must show **zero errors and zero warnings**. ESLint is configured to catch undefined variables, unused vars, unreachable code, and other semantic issues. **NEVER** commit or push code that fails this check. -2. Commit all verified code changes and push to `main`. -3. Create and push a new tag. **MANDATORY**: Tags MUST start with a `v` (e.g., `v1.4.0`). The GitHub Actions release workflow is strictly configured to ignore any tags without the `v` prefix. +2. Commit and push the verified product/release-note changes to `main`, then + wait for `verify`, `node20`, and `e2e` on the exact `origin/main` commit. + Markdown-only changes do not require browser or release gates. +3. From a clean, fast-forwarded `main`, create an annotated exact SemVer tag + (`git tag -a v1.4.0 -m "Release v1.4.0"`) on that same commit and push it once. - **🚫 TAG IMMUTABILITY**: Once a tag is pushed to `origin`, it is **PERMANENT**. You MUST **NEVER** reuse, move, or force-push an existing tag — not even to "fix" a mistake. If a release is missing a fix, increment the version and create a **new** tag (e.g., `v1.7.0` → `v1.7.1`). Tags are immutable identifiers; moving them breaks CI pipelines, corrupts the release history, and causes unreproducible builds. - **🚫 WHEN NOT TO TAG**: Do NOT create a release tag for changes that do NOT affect the shipped extension or server artifacts. Website text changes, documentation updates (`.md` files), and landing page content do NOT require a version tag. Tags trigger the full CI pipeline (Docker build, extension packaging, GitHub Release) — running this for a typo fix wastes CI resources and creates meaningless releases. Only tag when extension code (`extension/`), server code (`server/`), or shared protocol constants (`shared/`) have changed. -4. The CI will extract the version from the tag (e.g., `v1.4.0` → `1.4.0`), inject it into all source files, build the extension artifacts, publish the Docker image, and create a GitHub Release. -5. Verify the release builds on GitHub Actions. +4. The release workflow validates the tag, prepares and validates every version + source, pushes the generated version commit to `main`, and builds Chrome, + Firefox, website, and relay outputs from that exact prepared commit. +5. It creates a draft release, verifies archives, AMO output, checksums, + attestations, relay platforms/digest, and health before making the GitHub + Release public. +6. Verify GitHub assets, attestations, GHCR platforms/digest, and health smoke. ### 🚫 Force Push Policy > [!CAUTION] diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index d81f198..031a327 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -51,7 +51,9 @@ notification, command ACK, or relay media event. Force Sync remains a two-phase ACK protocol. A valid `PREPARE` is temporary room-wide choreography; the next authorized `EXECUTE` commits the latest target -visible to peers to canonical state. Per-sender +visible to peers to canonical state before the shared Force Sync timeout. The +offline queue replays an adjacent `PREPARE`/`EXECUTE` pair in one paced batch and +retains both if delivery fails. Per-sender `seq`, peer heartbeats, and the reconnect queue remain separate mechanisms. ## 3.2 Offline Media Intent diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index abee045..c54438c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to the KoalaSync browser extension and relay server. --- +## [v3.1.5] — 2026-08-25 + +This patch fixes room-exit cleanup and excessive popup width. + +### Fixed +- **Room exit cleanup** — Fully unhooks the selected tab after manual leaves, inactivity timeouts, and room closures. +- **Popup width** — Prevents dynamic content from stretching the popup on ultrawide displays. + +## [v3.1.4] — 2026-08-21 + +This patch restores player detection on pages that use boxless CSS wrappers, +including Crunchyroll, without adding site-specific matching. + +### Fixed +- **Extension: Crunchyroll playback synchronization** — Detects and controls + Crunchyroll's visible Bitmovin HTML5 player even though the page wraps it in + a boxless `display: contents` ancestor. The general fix keeps equivalent + players selectable on other sites while continuing to reject hidden players, + preloads, ads and background videos. +- **Extension: Player discovery during iframe churn** — Keeps stable nested + player frames discoverable when rapidly replaced ad frames make Chromium + reject an aggregate frame sweep, without restoring the `webNavigation` + permission or injecting globally into unselected tabs. + +### Testing +- **Regression coverage** — Adds unit and browser E2E coverage for visible + players inside `display: contents` wrappers. + +--- + ## [v3.1.3] — 2026-08-18 This release adds generic control for HTML5 players inside nested and cross-origin diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 5b5ccad..4cbbd0e 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -121,8 +121,9 @@ Only accepted, sanitized room controls update canonical state: established playback state. - a valid `force_sync_prepare` records only temporary coordination state. The next authorized `force_sync_execute` commits the latest room-wide prepared - target as playing. The latest valid prepare is also the only post-demotion - execute exemption in Host Control mode. + target as playing before `FORCE_SYNC_TIMEOUT` expires. Expired targets are + cleared and cannot alter canonical state. The latest valid prepare is also + the only post-demotion execute exemption in Host Control mode. `peer_status` heartbeats are observations and never rewrite canonical intent. Per-sender `seq` still orders commands from one sender; canonical `revision` @@ -168,6 +169,10 @@ This requires no event, capability, ACK, protocol-version, or minimum-version change. Old relays receive ordinary `play`/`pause`/`seek`; old peers see only the same existing relayed events. +Queued adjacent `force_sync_prepare` and `force_sync_execute` entries replay in +one paced batch. If either send fails, the full pair remains queued so a later +retry refreshes the prepared target before executing it. + ## Ephemeral encrypted chat Relays advertise chat support with `"chat-v1"` in `room_data.capabilities` and keep diff --git a/docs/SYNC_GUIDE.md b/docs/SYNC_GUIDE.md index df0f69b..b6397e5 100644 --- a/docs/SYNC_GUIDE.md +++ b/docs/SYNC_GUIDE.md @@ -35,7 +35,9 @@ The build script performs the following actions: The system enforces a strict `protocolVersion` check during the `JOIN_ROOM` handshake. - The version is defined in `shared/constants.js`. - If the extension and server versions mismatch, the server will reject the connection with an `Incompatible protocol version` error. -- **Never manually bump version numbers**. The CI pipeline automatically injects the version from the git tag into `manifest.base.json`, `shared/constants.js`, and `package.json` during release builds. Run the build script to synchronize other constant updates. +- Never edit release versions independently. The annotated SemVer tag workflow + updates and validates every version source, then pushes its generated version + commit directly to `main` before building the release. > [!CAUTION] > **NEVER** edit the files inside `extension/shared/` directly. They will be overwritten the next time the build script is run. Always edit the files in the root `shared/` directory and then run the build script. diff --git a/docs/TESTED_SERVICES.md b/docs/TESTED_SERVICES.md index 0c195f5..69d4b49 100644 --- a/docs/TESTED_SERVICES.md +++ b/docs/TESTED_SERVICES.md @@ -20,7 +20,7 @@ This document tracks which streaming platforms and media servers are supported b | **Disney+** | ✅ Full | ⚠️ Partial | ❌ | — | — | — | Series title only (e.g. "The Simpsons"), no episode info. | | **Prime Video** | ✅ Full | ✅ Full | ❌ | — | — | — | — | | **HBO Max / Max** | Not tested | Not tested | Not tested | — | — | — | — | -| **Crunchyroll** | Not tested | Not tested | Not tested | — | — | — | — | +| **Crunchyroll** (`crunchyroll.com`) | ✅ Full | ⚠️ Partial | ❌ | 2026-08-21 | Shik3i | v3.1.4 | Manual testing on the live service confirmed playback synchronization with its top-level Bitmovin HTML5 player. The Media Session exposes the episode as `E1 - Prologue` and the series as artist metadata, but the current episode parser does not recognize the abbreviated `E1` form for episode auto-sync. | | **Vimeo** | Not tested | Not tested | Not tested | — | — | — | — | | **Dailymotion** | Not tested | Not tested | Not tested | — | — | — | — | | **ARD / ZDF Mediathek** | Not tested | Not tested | Not tested | — | — | — | — | @@ -81,3 +81,8 @@ The currently verified cross-origin service topologies are: - **YummyAnime:** `yummyanime.tv` top page → same-origin wrapper → `thealloha.club` player. These embedded origins are implementation details of the services and may change independently. If the browser withholds access to a newly used player origin, KoalaSync asks for that origin through its normal site-access flow. + +Crunchyroll currently exposes its Bitmovin `