diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1778991..6e62e90 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,14 +17,13 @@ jobs:
quality:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- - run: npm run check
- - run: npm run package:artifact
+ - run: npm run release:check
- name: Upload packed artifact
uses: actions/upload-artifact@v7
with:
@@ -34,9 +33,10 @@ jobs:
include-hidden-files: true
node-22:
+ needs: quality
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
@@ -47,13 +47,14 @@ jobs:
- run: npm run test:package
browser:
+ needs: quality
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
@@ -62,3 +63,13 @@ jobs:
- run: npx playwright install --with-deps ${{ matrix.browser }}
- run: npm run build
- run: npm run test:browser -- --project=${{ matrix.browser }}
+ - name: Upload Playwright failure diagnostics
+ if: failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: playwright-${{ matrix.browser }}-failure
+ path: |
+ playwright-report/
+ test-results/
+ if-no-files-found: warn
+ retention-days: 7
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8d00c75..1617efa 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,7 +12,7 @@ on:
type: choice
options: [next, latest]
confirmation:
- description: Type publish VERSION with CHANNEL
+ description: Type publish 2.0.0 with latest
required: true
permissions:
@@ -22,20 +22,23 @@ jobs:
authorize:
runs-on: ubuntu-latest
steps:
- - name: Require main and exact confirmation
+ - name: Require main and exact stable release confirmation
env:
ACTUAL_REF: ${{ github.ref }}
+ ACTUAL_VERSION: ${{ inputs.version }}
+ ACTUAL_CHANNEL: ${{ inputs.channel }}
ACTUAL_CONFIRMATION: ${{ inputs.confirmation }}
- EXPECTED_CONFIRMATION: publish ${{ inputs.version }} with ${{ inputs.channel }}
run: |
test "$ACTUAL_REF" = "refs/heads/main"
- test "$ACTUAL_CONFIRMATION" = "$EXPECTED_CONFIRMATION"
+ test "$ACTUAL_VERSION" = "2.0.0"
+ test "$ACTUAL_CHANNEL" = "latest"
+ test "$ACTUAL_CONFIRMATION" = "publish 2.0.0 with latest"
verify:
needs: authorize
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
@@ -50,7 +53,20 @@ jobs:
env:
REQUESTED_CHANNEL: ${{ inputs.channel }}
run: node scripts/verify-release-channel.mjs "$REQUESTED_CHANNEL"
- - run: node scripts/verify-registry.mjs unpublished
+ - name: Verify 2.0.0 is absent from npm
+ run: node scripts/verify-registry.mjs unpublished
+ - name: Assert exactly one verified tarball
+ run: |
+ shopt -s nullglob
+ tarballs=(.artifacts/*.tgz)
+ test "${#tarballs[@]}" -eq 1
+ - name: Upload verified release artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: release-package-${{ github.sha }}
+ path: .artifacts/*.tgz
+ if-no-files-found: error
+ include-hidden-files: true
browser:
needs: verify
@@ -60,7 +76,7 @@ jobs:
matrix:
browser: [chromium, firefox, webkit]
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
@@ -78,19 +94,38 @@ jobs:
contents: read
id-token: write
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
- - run: npm ci
+ - name: Download verified release artifact
+ uses: actions/download-artifact@v8
+ with:
+ name: release-package-${{ github.sha }}
+ path: .artifacts
- name: Verify requested version
env:
REQUESTED_VERSION: ${{ inputs.version }}
run: test "$(node -p "require('./package.json').version")" = "$REQUESTED_VERSION"
- - run: npm run release:check
- - name: Publish through npm trusted publishing
- run: npm publish --access public --tag ${{ inputs.channel }}
+ - name: Select the single verified tarball
+ run: |
+ shopt -s nullglob
+ tarballs=(.artifacts/*.tgz)
+ test "${#tarballs[@]}" -eq 1
+ printf 'VERIFIED_TARBALL=%s\n' "${tarballs[0]}" >> "$GITHUB_ENV"
+ - name: Reconfirm 2.0.0 is absent from npm
+ run: node scripts/verify-registry.mjs unpublished
+ - name: Publish verified tarball for initial package bootstrap
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ REQUESTED_CHANNEL: ${{ inputs.channel }}
+ run: |
+ test -n "$NODE_AUTH_TOKEN"
+ npm publish "$VERIFIED_TARBALL" \
+ --access public \
+ --tag "$REQUESTED_CHANNEL" \
+ --provenance
- name: Verify registry propagation
run: node scripts/verify-registry.mjs published
@@ -100,7 +135,7 @@ jobs:
permissions:
contents: write
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
@@ -109,5 +144,6 @@ jobs:
run: >-
gh release create "v$RELEASE_VERSION"
--title "ReadonlyView v$RELEASE_VERSION"
+ --target "$GITHUB_SHA"
--generate-notes
${{ inputs.channel == 'next' && '--prerelease' || '' }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 755ef82..a9add8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 2.0.0 - 2026-09-03
+
### Breaking Changes
- Renamed `immuview` to `@nipe-solutions/readonly-view`.
@@ -27,7 +29,3 @@
### Documentation
- Rewritten around readonly-view ownership.
-
-## 2.0.0
-
-Prepared but not released. Promote Unreleased notes when publishing.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f596305..3f7a2a0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ Use Node.js 24 (`.nvmrc`) and npm.
```bash
npm ci
-npm run check
+npm run release:check
```
## Commands
@@ -18,3 +18,29 @@ npm run check
- `npm run build:website`: production documentation site.
Add a failing regression test before fixing a defect. Tests should name an observable break and exercise real behavior. Keep changes focused, document public behavior, and explain API, compatibility, size, or security impact in the pull request.
+
+## Deployment and release changes
+
+Vercel's Git integration creates previews for pull requests and deploys
+documentation from `main` to the custom domain
+`https://readonly-view.nipesolutions.com`. Changes to `vercel.json`, public URLs,
+legal routes, DNS expectations, or release automation must describe their preview
+and production verification in the pull request.
+
+Publishing is restricted to the protected GitHub environment `npm`. The first
+publication uses a short-lived granular `NPM_TOKEN` environment secret because
+npm requires the package to exist before trusted-publisher setup; only the
+publish job receives it, and it publishes the verified tarball with provenance.
+Afterward, revoke the token, delete the secret, and configure the trusted-
+publisher tuple `NIPE-Solutions/readonly-view`, `release.yml`, `npm` for future
+releases. The stable release is dispatched from `main` with exactly
+`version=2.0.0`, `channel=latest`, and
+`confirmation=publish 2.0.0 with latest`. After publication, verify the npm
+metadata and a fresh external consumer install, provenance, the SHA-targeted
+`v2.0.0` GitHub release, and every production route; see
+[the release runbook](docs/RELEASING.md) for the exact commands.
+
+Never attempt to overwrite or republish an npm version. A package defect requires
+a reviewed patch release (and a precise deprecation warning when appropriate); a
+site defect should be rolled back in Vercel and corrected on `main`. Do not
+deprecate or otherwise mutate the old `immuview` package.
diff --git a/README.md b/README.md
index 0941e2a..25794e1 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ Initial wrapping creates one proxy and does not walk the graph. Nested proxies a
## Documentation
-[Architecture](docs/architecture.md) · [API](docs/api.md) · [Supported types](docs/supported-types.md) · [Compatibility](docs/compatibility.md) · [Migration](docs/migration-v1-v2.md) · [Security](docs/security-and-trust.md) · [Contributing](CONTRIBUTING.md)
+[Documentation site](https://readonly-view.nipesolutions.com) · [Architecture](docs/architecture.md) · [API](docs/api.md) · [Supported types](docs/supported-types.md) · [Compatibility](docs/compatibility.md) · [Migration](docs/migration-v1-v2.md) · [Security](docs/security-and-trust.md) · [Contributing](CONTRIBUTING.md)
## License
diff --git a/SECURITY.md b/SECURITY.md
index a695a27..77c700b 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,4 +2,6 @@
Security fixes are supported for the latest published major version. Report vulnerabilities through GitHub private vulnerability reporting for `NIPE-Solutions/readonly-view`; do not publish exploit details in an issue.
+Canonical documentation URL: [readonly-view.nipesolutions.com](https://readonly-view.nipesolutions.com).
+
ReadonlyView is not a security sandbox. Unexpected source-reference escapes through supported paths, source mutation by the library, prototype pollution, or package-integrity failures are relevant reports. Existing source references, user function/getter side effects, and consumer-proxy behavior are outside the sandbox guarantee but may still reveal correctness defects.
diff --git a/docs/RELEASING.md b/docs/RELEASING.md
index cda841d..c671763 100644
--- a/docs/RELEASING.md
+++ b/docs/RELEASING.md
@@ -1,16 +1,110 @@
# Releasing
-Releases are manual and protected.
-
-1. Use Node 24 and run `npm ci`.
-2. Update version and promote Unreleased notes.
-3. Run `npm run release:check` and the browser matrix.
-4. Review `npm pack --dry-run`, declarations, budgets, and license.
-5. Configure npm trusted publishing for the `NIPE-Solutions/readonly-view`
- repository and the `release.yml` workflow, with the `npm` GitHub environment
- protected by required reviewers.
-6. Dispatch Release from `main` with the exact version, the `next` or `latest`
- channel, and the literal `publish VERSION with CHANNEL` confirmation.
-7. Verify registry propagation and create the matching GitHub release.
-
-Stable versions use `latest`; prereleases use `next`. Never publish during implementation.
+Releases are manual, protected, and run only from `main`. The current workflow is
+locked to the first stable release: `@nipe-solutions/readonly-view@2.0.0` on the
+`latest` npm channel. Its inputs remain versioned so a future release can update
+the explicit authorization gate without redesigning the workflow.
+
+## Hosted documentation
+
+Vercel's Git integration owns documentation deployments for the
+`NIPE-Solutions/readonly-view` repository. Pull requests receive preview
+deployments; a merge to the production branch, `main`, creates the production
+deployment using the committed `vercel.json`. The production project must have
+`readonly-view.nipesolutions.com` attached as its custom domain with valid DNS
+and TLS before npm publication. Verify the home page, all four legal routes,
+`/robots.txt`, and `/sitemap.xml` on that HTTPS origin.
+
+If a documentation deployment is defective, restore the previous known-good
+Vercel deployment immediately and follow with a reviewed revert or fix on
+`main`. Do not publish a package merely to repair the hosted documentation.
+
+## First-publication authentication
+
+Create a protected GitHub environment named `npm`, restrict deployments to
+`main`, and require a reviewer when the repository plan supports it. Because npm
+does not allow trusted-publisher setup until the package exists, the initial
+`2.0.0` publication uses a short-lived granular npm access token:
+
+1. Immediately before dispatch, create a granular token with the shortest
+ practical expiry. Scope its package and organization permissions only as
+ narrowly as npm permits for the initial `@nipe-solutions/readonly-view`
+ publication, grant the required package write access, and select the token's
+ `Bypass 2FA` option. Bypassing 2FA is required for this non-interactive direct
+ publication because GitHub Actions cannot enter a one-time password.
+2. Add it as an environment secret named `NPM_TOKEN` on the protected `npm`
+ environment, not as a repository-wide secret. Only the `publish` job reads
+ it.
+3. After `2.0.0` exists and all checks below pass, configure the npm trusted
+ publisher with this exact tuple:
+
+ - repository owner/name: `NIPE-Solutions/readonly-view`
+ - workflow filename: `release.yml`
+ - GitHub environment: `npm`
+
+4. Immediately after the trusted publisher is configured, delete the
+ `NPM_TOKEN` GitHub environment secret and revoke the granular token before
+ any further release work. For future versions, update the explicit release
+ authorization and remove `NODE_AUTH_TOKEN` from the publish step so npm
+ authenticates through the trusted publisher.
+
+The initial workflow uses the token for registry authentication and the
+publish job's OIDC permission for `--provenance`; it is not an OIDC-only
+publication. Never use a long-lived npm token.
+
+## Stable 2.0.0 procedure
+
+1. On the reviewed `main` commit, use Node 24 and run `npm ci`,
+ `npm run release:check`, and `npm run test:browser`.
+2. Review `npm pack --dry-run`, declarations, bundle budgets, license, and the
+ exact tarball. Confirm the production documentation checks above pass.
+3. Confirm `npm view @nipe-solutions/readonly-view@2.0.0 version` returns an npm
+ `E404`; an existing version must never be overwritten.
+4. Dispatch the protected workflow with exactly these inputs:
+
+ ```bash
+ gh workflow run release.yml --ref main \
+ -f version=2.0.0 \
+ -f channel=latest \
+ -f 'confirmation=publish 2.0.0 with latest'
+ ```
+
+5. Approve the `npm` environment deployment, then monitor the publish job. It
+ downloads the single tarball produced by the verified job, reconfirms
+ registry absence, publishes those exact bytes with provenance, waits for
+ registry propagation, and only then creates GitHub release `v2.0.0` at the
+ immutable workflow commit.
+6. Verify the public result:
+
+ ```bash
+ npm view @nipe-solutions/readonly-view@2.0.0 name version dist-tags.latest repository homepage
+ test -n "$(npm view @nipe-solutions/readonly-view@2.0.0 dist.attestations.url)"
+
+ consumer_dir="$(mktemp -d)"
+ (
+ cd "$consumer_dir"
+ npm init --yes >/dev/null
+ npm install --ignore-scripts --save-exact @nipe-solutions/readonly-view@2.0.0
+ node --input-type=module -e \
+ "import('@nipe-solutions/readonly-view').then(m => console.log(typeof m.readonlyView))"
+ )
+
+ for route in / /privacy/ /impressum/ /de/datenschutz/ /de/impressum/ /robots.txt /sitemap.xml; do
+ curl --fail --silent --show-error --location \
+ "https://readonly-view.nipesolutions.com${route}" >/dev/null
+ done
+
+ gh release view v2.0.0 --repo NIPE-Solutions/readonly-view \
+ --json tagName,targetCommitish,url
+ ```
+
+The metadata must report version `2.0.0`, `latest` must resolve to `2.0.0`, the
+homepage must be `https://readonly-view.nipesolutions.com`, the import must print
+`function` from the temporary consumer, the attestation URL must be nonempty,
+all production requests must succeed over HTTPS, and the GitHub release target
+must match the dispatched `main` SHA. Remove `consumer_dir` after inspection.
+
+npm versions are immutable: never attempt to republish `2.0.0`. If the package
+is defective, deprecate it with a precise warning when appropriate, publish a
+reviewed patch version, and move `latest` only through the guarded workflow. Do
+not deprecate or otherwise mutate the old `immuview` package.
diff --git a/docs/compatibility.md b/docs/compatibility.md
index 944d1ad..b437f8b 100644
--- a/docs/compatibility.md
+++ b/docs/compatibility.md
@@ -16,4 +16,6 @@
The emitted JavaScript targets ES2022. “Current browser” means the versions installed by the lockfile's Playwright release, not every historical browser with Proxy support.
+Canonical documentation URL: [readonly-view.nipesolutions.com](https://readonly-view.nipesolutions.com).
+
On macOS 14, the current Playwright WebKit bundle has an upstream launch-protocol incompatibility. Local configuration omits that one project on that OS; the Linux CI matrix remains authoritative for WebKit support.
diff --git a/docs/release-readiness.md b/docs/release-readiness.md
index 4fe960d..a317447 100644
--- a/docs/release-readiness.md
+++ b/docs/release-readiness.md
@@ -1,6 +1,10 @@
# ReadonlyView 2.0 release readiness
-Status: alpha implementation complete; main-branch CI and npm trusted-publisher configuration remain external release prerequisites. No package or website was published by this work.
+Status: the stable `2.0.0` release candidate passed its complete local gate on
+2026-09-03 using macOS 14.6.1 arm64, Node 24.20.0, and npm 11.19.0. Main-branch
+CI, Vercel production deployment, the protected `npm` environment, and
+first-publication authentication remain external release prerequisites. No
+package or website was published by this work.
## 1. Architecture
@@ -28,15 +32,46 @@ Separate top-level calls create separate membranes; passing an existing view ret
## 7. Tests
-The local suite currently contains 72 runtime tests across 21 files, plus strict compile-time tests. It covers mutation routes, descriptors, frozen/sealed/non-extensible values, liveness, identity, cycles, native adapters, randomized graphs, documentation examples, and regression cases from the final independent review. Coverage evidence: 91.34% statements, 89.11% branches, 93.10% functions, and 96.41% lines.
+The release-candidate run passed 77 runtime tests across 21 files and the strict
+compile-time suite. The package boundary run passed five more tests: one real
+packed-consumer test and four unpublished-registry classification tests. It
+covers mutation routes, descriptors, frozen/sealed/non-extensible values,
+liveness, identity, cycles, native adapters, randomized graphs, documentation
+examples, and regression cases from the final independent review. A fresh
+coverage run passed the same 77 tests and measured 91.34% statements, 89.11%
+branches, 93.10% functions, and 96.41% lines.
## 8. Package and build
-Vite emits ES2022 ESM and CommonJS builds; TypeScript plus Rollup emits one declaration entry. The real six-file npm tarball is installed into an isolated consumer and exercised through ESM, CommonJS, strict TypeScript, esbuild, tree shaking, and blocked private imports. Runtime dependencies: zero.
+Vite emits ES2022 ESM and CommonJS builds; TypeScript plus Rollup emits one
+declaration entry. The exact seven-file npm tarball contains `CHANGELOG.md`,
+`LICENSE`, `README.md`, `package.json`, `dist/index.js`, `dist/index.cjs`, and
+`dist/index.d.ts`. It contains no tests, website source, benchmarks, source
+maps, or private modules. The tarball is installed into an isolated consumer
+and exercised through ESM, CommonJS, strict TypeScript, esbuild, tree shaking,
+and blocked private imports. Runtime dependencies: zero.
+
+The inspected `.artifacts/nipe-solutions-readonly-view-2.0.0.tgz` is 6,971 B
+packed and 33,691 B unpacked. Its SHA-1 is
+`de84bb58a5ab30d6cfa3152f26d491e59d5aee82`; npm reported integrity
+`sha512-vXQXfQNoO7LeTxFexZxSDY93NUClX04ZRl1CFjH8jCIcBIkwrUGL5uD5wkpMYvX05PMjIv+XefQ8I9LSbcbQmw==`.
## 9. CI and release
-CI runs the Node 24 quality gate, Node 22 compatibility, and Chromium/Firefox/WebKit jobs. Release is manual, main-only, exact-confirmation gated, environment protected, checks version/channel policy and registry availability, repeats quality and browsers, uses npm OIDC trusted publishing, waits for propagation, then creates a GitHub release. Repository-side npm trusted-publisher and environment-reviewer settings must be configured before use.
+CI runs the Node 24 quality gate, Node 22 compatibility, and
+Chromium/Firefox/WebKit jobs. Release is manual, main-only, exact-confirmation
+gated, environment protected, checks version/channel policy and registry
+availability, carries the verified tarball through the browser gate, and waits
+for propagation before creating a SHA-targeted GitHub release. The first
+publication uses a short-lived granular `NPM_TOKEN` environment secret plus
+OIDC provenance. After the package exists, that token must be revoked and
+deleted and the documented npm trusted publisher must replace it for future
+releases.
+
+The local browser command passed all 22 scheduled tests: 11 on Chromium and 11
+on Firefox. Playwright deliberately excluded WebKit on this macOS 14 host under
+the repository's documented upstream-compatibility rule; the Linux CI WebKit
+job remains required.
## 10. Documentation
@@ -44,19 +79,40 @@ The repository contains a rewritten README, API, guarantees, compatibility, supp
## 11. Size
-Current measured artifacts: minified ESM 5,807 B (7,000 B budget), minified+gzip ESM 1,900 B (2,500 B), declarations 2,026 B (2,500 B), packed tarball 6,521 B (10,000 B).
+Release-candidate measurements: minified ESM 5,807 B (7,000 B budget),
+minified+gzip ESM 1,900 B (2,500 B), declarations 2,026 B (2,500 B), packed
+tarball 6,971 B (10,000 B), and unpacked tarball 33,691 B.
## 12. Benchmark summary
-On Node 24.20.0, macOS arm64, 100,000 iterations measured: view creation 26.20 ms, first nested access 77.04 ms, repeated nested read 15.38 ms, deep read 23.52 ms, array iteration 1,484.39 ms, Map read 20.69 ms, Set iteration 40.86 ms, and Date read 15.41 ms. These are diagnostic local results, not marketing claims; see the methodology for caveats.
+On Node 24.20.0, macOS arm64, 100,000 iterations measured: view creation 26.90
+ms, first nested access 77.62 ms, repeated nested read 15.34 ms, deep read 23.88
+ms, array iteration 1,446.00 ms, Map read 20.00 ms, Set iteration 40.35 ms, and
+Date read 16.05 ms. The required 100-iteration benchmark smoke also passed as
+part of `release:check`. These are diagnostic local results, not marketing
+claims; see the methodology for caveats.
## 13. Known limitations
-ReadonlyView is not a sandbox. Generic/overloaded callable types cannot always be transformed without losing signature information. Custom class views do not preserve `instanceof`; private fields can reject proxy receivers. Cross-realm and consumer-proxy behavior is limited by the supplied realm/proxy. WebKit could not launch under the local macOS 14 Playwright bundle and must be proven by the Linux CI job.
+ReadonlyView is not a sandbox. Generic/overloaded callable types cannot always
+be transformed without losing signature information. Custom class views do
+not preserve `instanceof`; private fields can reject proxy receivers.
+Cross-realm and consumer-proxy behavior is limited by the supplied realm/proxy.
+WebKit is excluded locally on macOS 14 and must be proven by the Linux CI job.
## 14. Recommended follow-up
-Run the branch on GitHub Actions, configure the npm trusted publisher and protected `npm` environment, decide the final alpha/stable version, promote `Unreleased` changelog notes, and only then invoke the manual release workflow. Bun, Deno, older TypeScript, and additional native adapters should be claimed only after dedicated CI.
+Push the reviewed branch and open a pull request; require green Node 24 quality,
+Node 22 compatibility, Chromium, Firefox, WebKit, and Vercel preview checks.
+After merge, require green `main` CI, link the Vercel project, configure the
+exact Vercel-provided GoDaddy DNS record, and verify HTTPS plus every production
+route. Then configure the protected `npm` environment and short-lived granular
+bootstrap token, dispatch the exact stable release, and verify registry
+propagation, provenance, dist-tags, consumer import, and the SHA-targeted GitHub
+release. Finally revoke and delete the bootstrap token and configure the
+documented npm trusted publisher for future OIDC releases. Bun, Deno, older
+TypeScript, and additional native adapters should be claimed only after
+dedicated CI.
## Local verification
@@ -66,4 +122,6 @@ npm run release:check
npm run test:browser
npm run coverage
npm run benchmark
+npm pack --dry-run --json
+tar -tzf .artifacts/nipe-solutions-readonly-view-2.0.0.tgz
```
diff --git a/docs/superpowers/plans/2026-09-03-legal-deployment-release.md b/docs/superpowers/plans/2026-09-03-legal-deployment-release.md
new file mode 100644
index 0000000..de06b74
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-03-legal-deployment-release.md
@@ -0,0 +1,409 @@
+# ReadonlyView Legal, Deployment, and Stable Release Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add bilingual legal pages, production Vercel deployment, reference-grade GitHub automation, and publish `@nipe-solutions/readonly-view@2.0.0` as a verified stable release.
+
+**Architecture:** Keep the Vite site framework-free and add four static legal entry points sharing the existing design system. Use Vercel Git integration for preview/production deployments and the guarded GitHub release workflow for initial token-authenticated publication with OIDC provenance after all local and hosted checks pass; switch future releases to npm trusted publishing after the package exists.
+
+**Tech Stack:** HTML, CSS, TypeScript, Vite 8, Playwright, GitHub Actions, Vercel, GoDaddy DNS, npm trusted publishing.
+
+**Spec:** `docs/superpowers/specs/2026-09-03-legal-deployment-release-design.md`
+
+## Global Constraints
+
+- Publish exactly `@nipe-solutions/readonly-view@2.0.0` publicly under `latest`.
+- Use `https://readonly-view.nipesolutions.com` as the production documentation origin.
+- Preserve zero runtime dependencies and the framework-free Vite website.
+- Reuse the approved NIPE Solutions e.U. legal operator data and bilingual reference text.
+- Do not publish until the release commit is merged to `main`, CI is green, and the exact tarball is verified.
+- Do not deprecate or otherwise mutate the old `immuview` npm package.
+
+---
+
+### Task 1: Bilingual legal routes and production website metadata
+
+**Files:**
+
+- Create: `website/privacy/index.html`
+- Create: `website/impressum/index.html`
+- Create: `website/de/datenschutz/index.html`
+- Create: `website/de/impressum/index.html`
+- Create: `website/legal.css`
+- Create: `website/public/robots.txt`
+- Create: `website/public/sitemap.xml`
+- Create: `website/public/favicon.svg`
+- Create: `website/public/favicon-32x32.png`
+- Create: `website/public/apple-touch-icon.png`
+- Create: `website/public/site.webmanifest`
+- Modify: `website/index.html`
+- Modify: `website/styles.css`
+- Modify: `vite.website.config.ts`
+- Modify: `scripts/verify-website.mjs`
+- Modify: `test/browser/website.spec.ts`
+- Modify: `test/integration/docs-examples.test.ts`
+
+**Interfaces:**
+
+- Consumes: existing `.masthead`, `.brand`, footer, color tokens, and `npm run build:website`.
+- Produces: four stable legal routes, language cross-links, canonical production URLs, and multi-page Vite output under `website/dist`.
+- Produces: a crisp ReadonlyView tab-icon family derived from the existing `R` brand mark.
+
+- [ ] **Step 1: Add failing build-verification assertions**
+
+Extend `scripts/verify-website.mjs` to read all five built HTML files and assert their defining content:
+
+```js
+const pages = new Map([
+ [
+ 'privacy/index.html',
+ ['Privacy Policy', 'Vercel Inc.', 'no non-essential cookies'],
+ ],
+ [
+ 'impressum/index.html',
+ ['Impressum', 'NIPE Solutions e.U.', 'ATU78464412'],
+ ],
+ [
+ 'de/datenschutz/index.html',
+ ['Datenschutzerklärung', 'Vercel Inc.', 'Datenschutzbehörde'],
+ ],
+ [
+ 'de/impressum/index.html',
+ ['Impressum', 'NIPE Solutions e.U.', 'FN 585066t'],
+ ],
+]);
+```
+
+Also assert that the main page links each route and no page contains `v2 alpha`.
+
+Assert that the home page contains examples for nested protection, owner-side live updates, arrays, Map, Set, Date, cycles, shared identity, and mutation rejection.
+
+Assert that all pages link `/favicon.svg`, `/favicon-32x32.png`, `/apple-touch-icon.png`, and `/site.webmanifest`, and that every referenced asset exists in `website/dist`.
+
+- [ ] **Step 2: Run the website check and confirm it fails**
+
+Run: `npm run test:website`
+
+Expected: failure because the legal build outputs do not exist.
+
+- [ ] **Step 3: Add the legal HTML and shared legal layout**
+
+Create semantic pages with one `h1`, a last-updated date, language switch, linked email/phone, company details, and the approved English/German privacy sections. Use a shared `.legal-shell`, `.legal-content`, and `.legal-meta` layout in `website/legal.css`; retain `:focus-visible` and a one-column mobile breakpoint.
+
+- [ ] **Step 4: Configure the Vite multi-page build**
+
+Change `vite.website.config.ts` to resolve these inputs:
+
+```ts
+rollupOptions: {
+ input: {
+ index: resolve(import.meta.dirname, 'website/index.html'),
+ privacy: resolve(import.meta.dirname, 'website/privacy/index.html'),
+ impressum: resolve(import.meta.dirname, 'website/impressum/index.html'),
+ datenschutz: resolve(import.meta.dirname, 'website/de/datenschutz/index.html'),
+ 'de-impressum': resolve(import.meta.dirname, 'website/de/impressum/index.html'),
+ },
+},
+```
+
+Use root-absolute asset and navigation URLs because production routes are nested.
+
+- [ ] **Step 5: Update the home page, examples, and crawl metadata**
+
+Create an original SVG favicon with a dark rounded-square core, white `R`, and blue membrane accent. Render deterministic 32×32 and 180×180 PNG variants from that SVG, add a minimal web manifest, and add favicon, Apple-touch, manifest, and theme-color tags to every page.
+
+Remove the hero release-status pill entirely. Move `v2` and `zero runtime dependencies` into the proof/metadata area, add canonical/OG production URLs, and add footer links to all legal pages. Add copyable examples for nested objects, owner-side live updates, arrays, Map, Set, Date, cycles, shared identity, and mutation rejection. Add `robots.txt` and a sitemap containing the five canonical URLs.
+
+Extend `test/integration/docs-examples.test.ts` with executable equivalents of the new examples, including:
+
+```ts
+const shared = { id: 1 };
+const source = {
+ items: [shared],
+ map: new Map([['selected', shared]]),
+ set: new Set([shared]),
+};
+const view = readonlyView(source);
+expect(view.items[0]).toBe(view.map.get('selected'));
+expect([...view.set][0]).toBe(view.items[0]);
+expect(() => Reflect.set(view.items[0], 'id', 2)).toThrow(DirectMutationError);
+```
+
+- [ ] **Step 6: Add browser route coverage**
+
+Add a Playwright test that opens each route and checks its heading, language link, footer, and absence of horizontal overflow:
+
+```ts
+for (const [route, heading] of legalRoutes) {
+ await page.goto(route);
+ await expect(
+ page.getByRole('heading', { level: 1, name: heading }),
+ ).toBeVisible();
+ expect(
+ await page.evaluate(() => document.body.scrollWidth),
+ ).toBeLessThanOrEqual(
+ await page.evaluate(() => document.documentElement.clientWidth),
+ );
+}
+```
+
+- [ ] **Step 7: Verify and commit**
+
+Run: `npm run test:website && npm run test:browser -- --project=chromium`
+
+Expected: all website and Chromium tests pass.
+
+Commit: `feat(website): add bilingual legal pages`
+
+### Task 2: Vercel configuration and public metadata
+
+**Files:**
+
+- Create: `vercel.json`
+- Modify: `package.json`
+- Modify: `package-lock.json`
+- Modify: `README.md`
+- Modify: `SECURITY.md`
+- Modify: `docs/compatibility.md`
+- Modify: `scripts/verify-release.mjs`
+
+**Interfaces:**
+
+- Consumes: `npm run build:website` and `website/dist` from Task 1.
+- Produces: repeatable Vercel builds and a single canonical documentation origin across metadata.
+
+- [ ] **Step 1: Add failing metadata verification**
+
+Extend `scripts/verify-release.mjs` with exact assertions:
+
+```js
+assert(
+ pkg.homepage === 'https://readonly-view.nipesolutions.com',
+ 'Homepage is stale',
+);
+const vercel = JSON.parse(await readFile(resolve(root, 'vercel.json'), 'utf8'));
+assert(
+ vercel.buildCommand === 'npm run build:website',
+ 'Unexpected Vercel build',
+);
+assert(vercel.outputDirectory === 'website/dist', 'Unexpected Vercel output');
+```
+
+- [ ] **Step 2: Confirm the release verifier fails**
+
+Run: `node scripts/verify-release.mjs`
+
+Expected: failure because package homepage and `vercel.json` are not production-ready.
+
+- [ ] **Step 3: Add Vercel configuration**
+
+Create:
+
+```json
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "buildCommand": "npm run build:website",
+ "framework": null,
+ "installCommand": "npm ci",
+ "outputDirectory": "website/dist"
+}
+```
+
+- [ ] **Step 4: Update public URLs and package version**
+
+Run `npm version 2.0.0 --no-git-tag-version`, set `homepage` to the production domain, and update README, security, and compatibility links. Confirm both `package-lock.json` version fields equal `2.0.0`.
+
+- [ ] **Step 5: Verify and commit**
+
+Run: `npm run build:website && node scripts/verify-release.mjs`
+
+Expected: both commands pass for `@nipe-solutions/readonly-view@2.0.0`.
+
+Commit: `chore(release): prepare v2 production metadata`
+
+### Task 3: Reference-grade CI and guarded release workflow
+
+**Files:**
+
+- Modify: `.github/workflows/ci.yml`
+- Modify: `.github/workflows/release.yml`
+- Modify: `docs/RELEASING.md`
+- Modify: `CONTRIBUTING.md`
+
+**Interfaces:**
+
+- Consumes: `npm run release:check`, Playwright projects, packed artifact output, npm channel verifier.
+- Produces: dependency-ordered CI, failure diagnostics, and a stable-only guarded release path using GitHub environment `npm`.
+
+- [ ] **Step 1: Validate workflow syntax before edits**
+
+Run: `npx prettier --check .github/workflows/ci.yml .github/workflows/release.yml`
+
+Expected: pass, establishing the existing baseline.
+
+- [ ] **Step 2: Align the CI workflow**
+
+Use `actions/checkout@v7`, retain `actions/setup-node@v7` and `actions/upload-artifact@v7`, change primary quality to `npm run release:check`, make Node 22 and browser jobs depend on `quality`, and upload `playwright-report/` plus `test-results/` only on browser failure. Keep Chromium, Firefox, WebKit, packed artifact, Node 22, concurrency, and read-only permissions.
+
+- [ ] **Step 3: Tighten stable release validation**
+
+Use `actions/checkout@v7`, keep exact confirmation `publish 2.0.0 with latest`, require `main`, use environment `npm`, grant `id-token: write` only to publish, verify `2.0.0` is absent before publish, carry exactly one verified tarball into the publish job, and verify registry propagation before a SHA-targeted GitHub release. Use a short-lived granular `NPM_TOKEN` environment secret only for the initial package bootstrap, with `--provenance`; remove it after trusted publishing can be configured. Preserve generic version inputs for future releases but enforce stable/latest through `verify-release-channel.mjs`.
+
+- [ ] **Step 4: Document hosted deployment and publication**
+
+Update `docs/RELEASING.md` and `CONTRIBUTING.md` with Vercel Git behavior, custom domain, npm environment/trusted-publisher tuple, exact dispatch inputs, post-publish checks, and rollback/non-republish guidance.
+
+- [ ] **Step 5: Verify and commit**
+
+Run: `npm run format:check && npm run lint && node scripts/verify-release-channel.mjs latest`
+
+Expected: all commands pass.
+
+Commit: `ci: align deployment and stable release gates`
+
+### Task 4: Finalize changelog and verify the release candidate
+
+**Files:**
+
+- Modify: `CHANGELOG.md`
+- Modify: `docs/release-readiness.md`
+
+**Interfaces:**
+
+- Consumes: completed Tasks 1–3 and version `2.0.0`.
+- Produces: an auditable stable changelog and exact local release evidence.
+
+- [ ] **Step 1: Promote release notes**
+
+Leave an empty `## Unreleased` section and move the complete v2 notes beneath `## 2.0.0 - 2026-09-03`, retaining Breaking Changes, Added, Fixed, Performance, and Documentation headings.
+
+- [ ] **Step 2: Run the practical local gate**
+
+Run: `npm ci && npm run release:check`
+
+Expected: formatting, lint, TypeScript, 65+ semantic tests, type tests, size, packed consumers, website verification, benchmark smoke, release metadata, and tarball creation all pass.
+
+- [ ] **Step 3: Run the browser matrix**
+
+Run: `npm run test:browser`
+
+Expected: Chromium, Firefox, and WebKit pass, except the repository's documented macOS 14 WebKit exclusion when applicable.
+
+- [ ] **Step 4: Inspect the exact package**
+
+Run: `npm pack --dry-run --json` and `tar -tzf .artifacts/nipe-solutions-readonly-view-2.0.0.tgz`
+
+Expected: only package metadata, README, LICENSE, changelog, and intended `dist` files; no tests, website source, benchmarks, source maps, or private modules.
+
+- [ ] **Step 5: Record measurements and commit**
+
+Update `docs/release-readiness.md` with exact test totals, tarball sizes, minified/gzip sizes, browser results, and the pending hosted steps.
+
+Commit: `docs: finalize readonly-view 2.0.0 release notes`
+
+### Task 5: Review, merge, and Vercel production deployment
+
+**Files:**
+
+- External: GitHub pull request and CI
+- External: Vercel project for `NIPE-Solutions/readonly-view`
+- External: GoDaddy DNS for `nipesolutions.com`
+
+**Interfaces:**
+
+- Consumes: reviewed release branch and `vercel.json`.
+- Produces: green `main`, Vercel preview/production deployments, DNS verification, and valid HTTPS at the production origin.
+
+- [ ] **Step 1: Push and open a focused pull request**
+
+Push the release-preparation branch, open a PR against `main`, and include legal routes, workflow deltas, local gate evidence, package version, and explicit statement that npm is not yet published.
+
+- [ ] **Step 2: Review CI and preview deployment**
+
+Wait for quality, Node 22, Chromium, Firefox, WebKit, and Vercel preview checks. Open the preview and verify the home page plus all legal routes on desktop and mobile.
+
+- [ ] **Step 3: Merge and verify production CI**
+
+Merge only after all required checks pass, then wait for the `main` CI run to finish successfully.
+
+- [ ] **Step 4: Link the Vercel project**
+
+In the authenticated Vercel account, import `NIPE-Solutions/readonly-view`, select the appropriate NIPE Solutions team, confirm production branch `main`, and verify that Vercel reads the committed build and output settings.
+
+- [ ] **Step 5: Configure the custom domain**
+
+Add `readonly-view.nipesolutions.com` to the Vercel project, read the exact required DNS record, create that record in the authenticated GoDaddy account, and wait for Vercel to report a valid configuration and issued TLS certificate.
+
+- [ ] **Step 6: Verify production**
+
+Check status 200, canonical URL, title, home interaction, all four legal routes, sitemap, robots, and HTTPS using both browser inspection and HTTP requests.
+
+### Task 6: npm trusted publishing and irreversible stable release
+
+**Files:**
+
+- External: GitHub environment `npm`
+- External: npm organization/package trusted-publisher settings
+- External: GitHub Actions Release run
+- External: npm package and GitHub release
+
+**Interfaces:**
+
+- Consumes: green merge commit on `main`, exact version `2.0.0`, npm organization access, production documentation.
+- Produces: public `@nipe-solutions/readonly-view@2.0.0`, `latest` dist-tag, provenance, and GitHub `v2.0.0` release.
+
+- [ ] **Step 1: Configure GitHub release protection**
+
+Create the repository environment `npm`, restrict it to `main`, and add the authenticated owner as a required reviewer when the repository plan supports reviewers. Do not add a long-lived npm token.
+
+- [ ] **Step 2: Configure first-publication authentication**
+
+Create a short-lived granular npm token with only the required `@nipe-solutions` package-write access and store it as the `NPM_TOKEN` secret on the protected `npm` environment. The initial workflow uses that token only in the publish job and uses OIDC for `--provenance`; it is not OIDC-only.
+
+- [ ] **Step 3: Reconfirm registry absence and commit identity**
+
+Run: `npm view @nipe-solutions/readonly-view@2.0.0 version`
+
+Expected before publication: npm `E404`. Confirm the release workflow SHA equals the reviewed green `main` SHA.
+
+- [ ] **Step 4: Dispatch the release**
+
+Run:
+
+```bash
+gh workflow run release.yml --ref main \
+ -f version=2.0.0 \
+ -f channel=latest \
+ -f 'confirmation=publish 2.0.0 with latest'
+```
+
+Approve the protected `npm` environment deployment when prompted and monitor every job through registry propagation and GitHub release creation.
+
+- [ ] **Step 5: Verify the public package**
+
+Run:
+
+```bash
+npm view @nipe-solutions/readonly-view@2.0.0 name version dist-tags.latest repository homepage
+test -n "$(npm view @nipe-solutions/readonly-view@2.0.0 dist.attestations.url)"
+consumer_dir="$(mktemp -d)"
+(
+ cd "$consumer_dir"
+ npm init --yes >/dev/null
+ npm install --ignore-scripts --save-exact @nipe-solutions/readonly-view@2.0.0
+ node --input-type=module -e \
+ "import('@nipe-solutions/readonly-view').then(m => console.log(typeof m.readonlyView))"
+)
+gh release view v2.0.0 --repo NIPE-Solutions/readonly-view \
+ --json tagName,targetCommitish,url
+```
+
+Expected: exact name/version, `latest` equals `2.0.0`, production homepage, the temporary consumer import prints `function`, provenance is visible in npm, and the GitHub release targets the dispatched SHA.
+
+- [ ] **Step 6: Switch future releases to trusted publishing**
+
+In npm package settings, configure repository `NIPE-Solutions/readonly-view`, workflow `release.yml`, environment `npm`. Delete the `NPM_TOKEN` GitHub environment secret, revoke the granular token, and remove `NODE_AUTH_TOKEN` from the publish step when preparing the next release.
+
+- [ ] **Step 7: Record final release evidence**
+
+Update the release-readiness record or GitHub release notes with the production site, CI run, npm version, provenance, tarball integrity, and known limitations. Do not modify the old `immuview` package.
diff --git a/docs/superpowers/specs/2026-09-03-legal-deployment-release-design.md b/docs/superpowers/specs/2026-09-03-legal-deployment-release-design.md
new file mode 100644
index 0000000..4b93d68
--- /dev/null
+++ b/docs/superpowers/specs/2026-09-03-legal-deployment-release-design.md
@@ -0,0 +1,69 @@
+# ReadonlyView Legal, Deployment, and Stable Release Design
+
+**Status:** Approved on 2026-09-03
+
+## Outcome
+
+ReadonlyView will expose complete bilingual legal pages, deploy its Vite documentation site through Vercel at `https://readonly-view.nipesolutions.com`, align repository automation with the NIPE Solutions reference repository, and publish `@nipe-solutions/readonly-view@2.0.0` under npm's `latest` tag.
+
+## Legal pages
+
+The site will publish four stable routes:
+
+- `/privacy/`
+- `/impressum/`
+- `/de/datenschutz/`
+- `/de/impressum/`
+
+The text and operator data will be adapted from the NIPE Solutions React Spring Bottom Sheet v5 site. The operator is NIPE Solutions e.U., proprietor Nicholas Petrasek, Achtergasse 10, 1230 Wien, Austria. Contact, VAT, company-register, court, authority, chamber, and trade details remain identical to the approved reference pages. Privacy text will describe Vercel hosting, ordinary request logs, email/contact handling, no non-essential cookies or analytics, GDPR rights, and the Austrian data-protection authority. The pages must not claim legal guarantees beyond the source text.
+
+Legal pages use the established ReadonlyView visual system: shared masthead and footer, narrow readable measure, explicit language switching, keyboard-visible focus styles, and responsive layouts. The main footer links all English and German legal routes.
+
+## Website architecture
+
+The existing framework-free Vite website remains in place. Legal pages are static HTML entry points with shared CSS; Vite is configured as a multi-page build so route directories produce deployable `index.html` files. The site remains zero-JavaScript on legal routes and retains the current interactive module only on the documentation home page.
+
+The hero contains only the product proposition, install command, and interactive ownership demonstration. It must not use a release-status pill or the phrase `v2 alpha · zero runtime dependencies`. Version and zero-dependency facts belong in the quieter proof/metadata area.
+
+The documentation home includes concise, copyable code examples for nested objects, owner-side live updates, arrays, Map, Set, Date, circular references, shared identity, and rejected mutations. Examples use the real public API, remain short enough to scan, and are mirrored by automated documentation-example tests rather than presented as decorative code.
+
+Production metadata, canonical URLs, Open Graph URLs, sitemap, robots file, package homepage, and repository documentation use `https://readonly-view.nipesolutions.com`.
+
+The tab icon uses the established ReadonlyView brand mark rather than a generic shield or lock: a dark rounded-square `R` core with a blue membrane accent, simplified for legibility at 16×16. The site ships a scalable SVG favicon plus raster browser and Apple-touch variants, with theme colors and manifest metadata shared by every page.
+
+## Vercel and DNS
+
+The repository will include a `vercel.json` with `npm ci`, `npm run build:website`, and `website/dist` output. The GitHub repository will be connected to Vercel using Git integration: pull requests receive preview deployments and `main` is the production branch. The custom domain is `readonly-view.nipesolutions.com`; its exact Vercel-provided DNS record will be configured in GoDaddy and verified rather than guessed.
+
+## GitHub workflows
+
+Automation follows the reference repository's sequencing and supply-chain model without importing React-specific jobs:
+
+- current pinned major versions for checkout, Node setup, and artifact upload;
+- Node 24 primary quality gate and retained Node 22 compatibility gate;
+- browser jobs depend on quality and cover Chromium, Firefox, and WebKit;
+- Playwright diagnostics upload only on failure;
+- release runs only from `main`, requires exact version/channel/confirmation, verifies package and registry state, uses the protected `npm` environment and OIDC trusted publishing, verifies propagation, and creates a GitHub release;
+- packed artifact verification, declaration checks, size budgets, website build, and benchmarks remain required.
+
+Vercel deployment uses Vercel Git integration, not duplicated GitHub Actions deployment secrets.
+
+## Stable npm publication
+
+The package name is `@nipe-solutions/readonly-view`, version `2.0.0`, visibility public, and distribution tag `latest`. Package metadata and the lockfile must agree. The changelog will contain a real `2.0.0` entry and a fresh empty `Unreleased` section.
+
+Before publication, the exact packed artifact must pass the full release check and browser matrix. npm trusted publishing will be configured for `NIPE-Solutions/readonly-view`, workflow `release.yml`, environment `npm`, if npm permits initial publication through OIDC. If the registry requires an authenticated first publication, the first release may use the already authenticated npm organization account, but it must still publish the verified artifact with provenance when supported. Publication is followed by registry import checks, provenance inspection, and a GitHub `v2.0.0` release.
+
+The old `immuview` package is outside this change and will be deprecated separately by the owner.
+
+## Release order
+
+1. Add and test legal pages and production metadata.
+2. Align workflows and release documentation.
+3. Run the full local quality, package, website, and browser gates.
+4. Merge the reviewed release-preparation branch to `main` and wait for green CI.
+5. Link and deploy Vercel, configure GoDaddy DNS, and verify HTTPS/routes.
+6. Configure npm/GitHub trusted publishing and protection.
+7. Dispatch the stable release and verify npm and GitHub release state.
+
+Public npm publication must not precede a green release commit on `main`.
diff --git a/package-lock.json b/package-lock.json
index ea8f273..85db9e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@nipe-solutions/readonly-view",
- "version": "2.0.0-alpha.0",
+ "version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@nipe-solutions/readonly-view",
- "version": "2.0.0-alpha.0",
+ "version": "2.0.0",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^10.0.1",
diff --git a/package.json b/package.json
index 3268feb..d260243 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@nipe-solutions/readonly-view",
- "version": "2.0.0-alpha.0",
+ "version": "2.0.0",
"packageManager": "npm@11.19.0",
"description": "A deeply readonly, lazy, live view over mutable JavaScript data.",
"type": "module",
@@ -16,7 +16,8 @@
"./package.json": "./package.json"
},
"files": [
- "dist"
+ "dist",
+ "CHANGELOG.md"
],
"sideEffects": false,
"engines": {
@@ -41,7 +42,7 @@
"test": "npm run test:unit",
"test:unit": "vitest run --project unit --passWithNoTests",
"test:types": "tsc --project test/types/tsconfig.json --noEmit",
- "test:package": "npm run build:dist && node --test test/package/verify-package.test.mjs",
+ "test:package": "npm run build:dist && node --test test/package/verify-package.test.mjs test/package/verify-registry.test.mjs",
"test:size": "npm run build:dist && node scripts/check-bundle-size.mjs",
"test:website": "npm run build:website && node scripts/verify-website.mjs",
"test:browser": "playwright test",
@@ -57,7 +58,7 @@
],
"author": "Nicholas Petrasek",
"license": "MIT",
- "homepage": "https://github.com/NIPE-Solutions/readonly-view#readme",
+ "homepage": "https://readonly-view.nipesolutions.com",
"repository": {
"type": "git",
"url": "git+https://github.com/NIPE-Solutions/readonly-view.git"
diff --git a/playwright.config.ts b/playwright.config.ts
index 9c31d33..c7e3a0c 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -16,9 +16,17 @@ export default defineConfig({
reporter: process.env.CI ? [['html', { open: 'never' }], ['line']] : 'line',
use: { baseURL: 'http://127.0.0.1:4173', trace: 'on-first-retry' },
projects,
- webServer: {
- command: 'npm run build:dist && vite --host 127.0.0.1 --port 4173',
- port: 4173,
- reuseExistingServer: !process.env.CI,
- },
+ webServer: [
+ {
+ command: 'vite --host 127.0.0.1 --port 4173 --strictPort',
+ port: 4173,
+ reuseExistingServer: !process.env.CI,
+ },
+ {
+ command:
+ 'npm run build:website && vite preview --config vite.website.config.ts --host 127.0.0.1 --port 42873 --strictPort',
+ port: 42873,
+ reuseExistingServer: !process.env.CI,
+ },
+ ],
});
diff --git a/scripts/verify-registry.mjs b/scripts/verify-registry.mjs
index 510a48c..68e0c25 100644
--- a/scripts/verify-registry.mjs
+++ b/scripts/verify-registry.mjs
@@ -10,7 +10,15 @@ const pkg = JSON.parse(
const specifier = `${pkg.name}@${pkg.version}`;
const mode = process.argv[2];
-async function registryVersion() {
+function npmErrorCode(error) {
+ try {
+ return JSON.parse(error.stdout)?.error?.code;
+ } catch {
+ return undefined;
+ }
+}
+
+async function registryVersion({ allowNotFound = false } = {}) {
try {
const { stdout } = await exec('npm', [
'view',
@@ -19,19 +27,20 @@ async function registryVersion() {
'--json',
]);
return JSON.parse(stdout);
- } catch {
- return undefined;
+ } catch (error) {
+ if (allowNotFound && npmErrorCode(error) === 'E404') return undefined;
+ throw error;
}
}
if (mode === 'unpublished') {
- if ((await registryVersion()) !== undefined) {
+ if ((await registryVersion({ allowNotFound: true })) !== undefined) {
throw new Error(`${specifier} already exists on npm`);
}
console.log(`${specifier} is available on npm`);
} else if (mode === 'published') {
for (let attempt = 1; attempt <= 12; attempt += 1) {
- if ((await registryVersion()) === pkg.version) {
+ if ((await registryVersion({ allowNotFound: true })) === pkg.version) {
console.log(`${specifier} is visible on npm`);
process.exit(0);
}
diff --git a/scripts/verify-release.mjs b/scripts/verify-release.mjs
index 8d3e19d..e022172 100644
--- a/scripts/verify-release.mjs
+++ b/scripts/verify-release.mjs
@@ -12,6 +12,11 @@ function assert(condition, message) {
}
assert(pkg.name === '@nipe-solutions/readonly-view', 'Unexpected package name');
+assert(
+ pkg.homepage === 'https://readonly-view.nipesolutions.com',
+ 'Homepage is stale',
+);
+assert(pkg.version === '2.0.0', 'Unexpected package version');
assert(pkg.version === lock.version, 'package-lock root version is stale');
assert(
pkg.version === lock.packages[''].version,
@@ -19,7 +24,9 @@ assert(
);
assert(pkg.license === 'MIT', 'Package license must be MIT');
assert(
- pkg.files.length === 1 && pkg.files[0] === 'dist',
+ pkg.files.length === 2 &&
+ pkg.files.includes('dist') &&
+ pkg.files.includes('CHANGELOG.md'),
'files allowlist changed',
);
assert(pkg.sideEffects === false, 'Package must remain side-effect free');
@@ -28,6 +35,15 @@ assert(
'Repository metadata is stale',
);
+const vercel = JSON.parse(await readFile(resolve(root, 'vercel.json'), 'utf8'));
+assert(
+ vercel.buildCommand === 'npm run build:website',
+ 'Unexpected Vercel build',
+);
+assert(vercel.framework === null, 'Unexpected Vercel framework');
+assert(vercel.installCommand === 'npm ci', 'Unexpected Vercel install');
+assert(vercel.outputDirectory === 'website/dist', 'Unexpected Vercel output');
+
for (const file of [
'LICENSE',
'README.md',
diff --git a/scripts/verify-website.mjs b/scripts/verify-website.mjs
index 23f55f1..bda7892 100644
--- a/scripts/verify-website.mjs
+++ b/scripts/verify-website.mjs
@@ -1,9 +1,40 @@
import { access, readFile } from 'node:fs/promises';
-const html = await readFile(
- new URL('../website/dist/index.html', import.meta.url),
- 'utf8',
-);
+const distUrl = new URL('../website/dist/', import.meta.url);
+const pages = new Map([
+ [
+ 'privacy/index.html',
+ ['Privacy Policy', 'Vercel Inc.', 'no non-essential cookies'],
+ ],
+ [
+ 'impressum/index.html',
+ ['Impressum', 'NIPE Solutions e.U.', 'ATU78464412'],
+ ],
+ [
+ 'de/datenschutz/index.html',
+ ['Datenschutzerklärung', 'Vercel Inc.', 'Datenschutzbehörde'],
+ ],
+ [
+ 'de/impressum/index.html',
+ ['Impressum', 'NIPE Solutions e.U.', 'FN 585066t'],
+ ],
+]);
+const pageHtml = new Map([
+ ['index.html', await readFile(new URL('index.html', distUrl), 'utf8')],
+]);
+
+for (const [path, expectedContent] of pages) {
+ const html = await readFile(new URL(path, distUrl), 'utf8');
+ pageHtml.set(path, html);
+ for (const content of expectedContent) {
+ if (!html.includes(content))
+ throw new Error(`${path} is missing defining content: ${content}`);
+ }
+}
+
+const home = pageHtml.get('index.html');
+if (home === undefined) throw new Error('Missing built home page');
+
for (const heading of [
'Ownership model',
'Supported types',
@@ -12,10 +43,130 @@ for (const heading of [
'Migration from ImmuView v1',
'Security and trust model',
]) {
- if (!html.includes(heading))
+ if (!home.includes(heading))
throw new Error('Missing website section: ' + heading);
}
-if (html.includes('react'))
+
+for (const route of pages.keys()) {
+ const publicRoute = `/${route.replace('index.html', '')}`;
+ if (!home.includes(`href="${publicRoute}"`))
+ throw new Error('Home page does not link legal route: ' + publicRoute);
+}
+
+for (const example of [
+ 'Nested protection',
+ 'Owner-side live updates',
+ 'Arrays',
+ 'Map and Set',
+ 'Date',
+ 'Circular references',
+ 'Shared identity',
+ 'Mutation rejection',
+]) {
+ if (!home.includes(example))
+ throw new Error('Missing website example: ' + example);
+}
+
+function exampleCard(heading) {
+ const marker = `
+ Diese Erklärung beschreibt die begrenzte Verarbeitung, die
+ für Bereitstellung und Schutz dieser statischen
+ Dokumentationswebsite erforderlich ist.
+
+ Beim Abruf können technisch erforderliche Angaben
+ wie URL, Zeitpunkt, IP-Adresse, Browserinformationen
+ und Antwortstatus in Server- und
+ Sicherheitsprotokollen verarbeitet werden. Zweck
+ sind sichere Auslieferung, Fehleranalyse und
+ Missbrauchsschutz. Rechtsgrundlage ist das
+ berechtigte Interesse am sicheren und zuverlässigen
+ Betrieb gemäß Art. 6 Abs. 1 lit. f DSGVO.
+
+
+
+
Hosting und Empfänger
+
+ Hosting-Anbieter ist Vercel Inc. Vercel und dessen
+ Unterauftragsverarbeiter können Anfragedaten unter
+ den jeweils anwendbaren Garantien außerhalb des EWR
+ verarbeiten. Details stehen in der
+ Datenschutzerklärung von Vercel
+ und im
+ Auftragsverarbeitungsnachtrag.
+
+
+
+
Cookies und Analyse
+
+ Diese Projektwebsite setzt bewusst keine nicht
+ erforderlichen Cookies ein und verwendet keinen
+ Produktanalysedienst. Für externe Ziele wie GitHub
+ und npm gelten nach dem Aufruf deren eigene
+ Datenschutzbestimmungen.
+
+
+
+
Kontakt und Speicherdauer
+
+ Bei einer Kontaktaufnahme per E-Mail werden die
+ übermittelten Daten zur Bearbeitung der Anfrage und
+ nur so lange gespeichert, wie dies dafür oder
+ aufgrund gesetzlicher Pflichten erforderlich ist.
+ Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO,
+ soweit die Anfrage einen Vertrag oder
+ vorvertragliche Maßnahmen betrifft, andernfalls Art.
+ 6 Abs. 1 lit. f DSGVO—das berechtigte Interesse an
+ der Beantwortung von Projekt- und
+ Geschäftskorrespondenz. Ein E-Mail-Dienstleister
+ kann die Nachricht im Auftrag und, soweit
+ erforderlich, unter geeigneten Garantien für
+ internationale Übermittlungen verarbeiten.
+ Betriebsprotokolle werden nach den konfigurierten
+ Sicherheits- und Servicefristen des
+ Hosting-Anbieters begrenzt.
+
+
+
+
Ihre Rechte
+
+ Unter den Voraussetzungen der DSGVO bestehen
+ insbesondere Rechte auf Auskunft, Berichtigung,
+ Löschung, Einschränkung, Datenübertragbarkeit und
+ Widerspruch. Beschwerden können an die
+ Österreichische Datenschutzbehörde
+ gerichtet werden. Zur Ausübung eines Rechts
+ kontaktieren Sie den Verantwortlichen.
+
+ UID-Nummer: ATU78464412 Firmenbuchnummer:
+ FN 585066t Firmenbuchgericht:
+ Handelsgericht Wien Firmensitz: Wien
+
+
+
+
Gewerbe und Aufsicht
+
+ Gewerbe: Dienstleistungen in der automatischen
+ Datenverarbeitung und Informationstechnik.
+ Aufsichts- und Gewerbebehörde: Magistratisches
+ Bezirksamt für den 23. Bezirk. Mitglied der
+ Wirtschaftskammer Wien. Anwendbare Rechtsvorschrift
+ ist die Gewerbeordnung, abrufbar im
+ Rechtsinformationssystem des Bundes.
+
+
+
+
Blattlinie
+
+ Technische Dokumentation, Beispiele und
+ Projektinformationen zur Open-Source-Bibliothek
+ ReadonlyView. Medieninhaber und verantwortlich für
+ den Inhalt: NIPE Solutions e.U., Achtergasse 10,
+ 1230 Wien.
+
+ VAT ID: ATU78464412 Company register number: FN
+ 585066t Company register court: Handelsgericht
+ Wien Registered office: Wien
+
+
+
+
Trade and supervision
+
+ Trade: Dienstleistungen in der automatischen
+ Datenverarbeitung und Informationstechnik.
+
+
+ Supervisory and trade authority: Magistratisches
+ Bezirksamt für den 23. Bezirk. Chamber membership:
+ Wirtschaftskammer Wien. Applicable trade law is the
+ Austrian Trade Regulation Act (GewO), available
+ through the
+ Austrian Legal Information System.
+
+
+
+
Editorial direction
+
+ Technical documentation, examples, and project
+ information for the open-source ReadonlyView
+ library. Media owner and editorial responsibility:
+ NIPE Solutions e.U., Achtergasse 10, 1230 Wien.
+
+
+
+
Project and external links
+
+ The software is provided under the MIT License.
+ Links to external websites, including GitHub and
+ npm, lead to services operated under their
+ respective providers’ terms and privacy information.
+
+ The controller is NIPE Solutions e.U., Achtergasse
+ 10, 1230 Wien, Austria. Contact:
+ office@nipesolutions.com.
+
+
+
+
Website delivery and security
+
+ When you request a page, technically necessary
+ information such as the requested URL, timestamp, IP
+ address, browser information, and response status
+ may be processed in server and security logs. This
+ supports reliable delivery, fault diagnosis, and
+ protection against abuse. The legal basis is the
+ controller’s legitimate interest in secure and
+ reliable operation under Article 6(1)(f) GDPR.
+
+
+
+
Hosting and recipients
+
+ The website is hosted by Vercel Inc. Vercel and its
+ subprocessors may process request data outside the
+ European Economic Area under applicable transfer
+ safeguards. Current provider details are available
+ in the
+ Vercel privacy policy
+ and
+ data processing addendum.
+
+
+
+
Cookies and analytics
+
+ There are no non-essential cookies intentionally set
+ by this project website, and it does not run a
+ product analytics service. External destinations
+ such as GitHub and npm apply their own privacy terms
+ after you follow a link to them.
+
+
+
+
Contact and retention
+
+ If you contact the operator by email, the supplied
+ contact and message data is processed to answer your
+ request and retained only as long as needed for that
+ purpose or applicable legal obligations. The legal
+ basis is Article 6(1)(b) GDPR where the request
+ concerns a contract or steps before a contract, and
+ otherwise Article 6(1)(f) GDPR—the legitimate
+ interest in answering project and business
+ correspondence. An email service provider may
+ process the message on the controller’s behalf and,
+ where applicable, under appropriate international
+ transfer safeguards. Operational log retention is
+ limited by the hosting provider’s configured service
+ and security requirements.
+
+
+
+
Your rights
+
+ Subject to the GDPR’s conditions, you may request
+ access, rectification, erasure, restriction,
+ portability, or object to processing. You may
+ complain to the
+ Austrian Data Protection Authority. Contact the controller to exercise a right.
+