Production-readiness cleanup: package fixes, site hardening, tests & CI - #10
Conversation
- Update the v0.4.0 SHA-256 to the correct checksum. - Declare the formula macOS-only and Apple-Silicon-only via depends_on :macos and depends_on arch: :arm64, placed before the platform blocks. - Replace the inline Hardware::CPU.arm? conditional with nested on_macos/on_arm DSL blocks. - Assert against version.to_s in the test instead of a literal "0.4.0". - Mirror the dependency/architecture structure in the README formula template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ds_on - Update cask to version 2.0.1 with its SHA-256 (based on the origin/wolfwave-v2.0.1 bump). - Add verified: "github.com/MrDemonWolf/wolfwave/" to the download URL since the homepage (mrdemonwolf.github.io) is on a different domain. - Replace the deprecated depends_on macos: ">= :tahoe" with the supported depends_on macos: :tahoe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bump vitest to ^3.2.7 (resolved 3.2.7). - Refresh package-lock.json; resolve advisories in vite, rollup, postcss, undici, picomatch, and esbuild via compatible non-breaking upgrades. - npm audit now reports 0 vulnerabilities. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Security & reliability: - Route release-history data through server-side escaping and HTML-escape the item name in <title>/<meta>/breadcrumb; add U+2028/U+2029 handling to json_escape (bash-3.2-safe octal) so no untrusted value can break out of a <script> block. - Add a safe_url() scheme allowlist (http/https/mailto) for homepage and release-notes links. - fetch_releases now uses curl connect/read timeouts + bounded retries, matches release tags with or without a leading "v", and paginates. - Honor OFFLINE=1 (skip network) and STRICT_RELEASES/CI (fail rather than silently publish pages with version history removed). - Run Tailwind via `npx --no-install` so the build never downloads a package. Server-rendering: - Render name, version, description, install command, homepage, caveats, stability, and the full version-history table into HTML at build time via new template placeholders. Detail pages are now fully useful with JavaScript disabled; the inline script only enhances (search/copy/theme/ active-section tracking). - Add an OUT_DIR knob so builds can target an isolated directory. Also resolve the lone ShellCheck warning and document the Python 3 / offline build requirements in the README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Search dialog: visible close button, combobox/listbox semantics (role, aria-expanded, aria-activedescendant, aria-selected), an aria-live result-count announcer, and a real focus trap that cycles through every control instead of forcing focus back to the input. Focus still restores to the trigger on close. - escapeHtml also neutralizes U+2028/U+2029; add a safeUrl() scheme allowlist used for search-result hrefs. - Clipboard writes feature-detect navigator.clipboard and fall back to execCommand without throwing; theme localStorage reads/writes are wrapped so private-mode contexts don't break theme init. - Remove the now-dead initDetailPage/stabilityBadge client renderer (content is server-rendered); detectStability stays as the tested reference classifier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Homebrew audits description length; the previous 85-char desc would fail brew audit --strict. Trim to 75 chars and update the README cask table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- runBuild() now builds into an isolated temp dir (never reuses or deletes
a user's _site) and runs OFFLINE for deterministic, network-free tests;
bump vitest hookTimeout to match the build time.
- Derive expected metadata from Formula/*.rb and Casks/*.rb instead of
hard-coding package names and versions; data-drive the build-output and
html-content suites.
- Assert essential detail-page content is server-rendered (present before
any script runs) and that NO {{...}} placeholder survives in any page.
- Add cask-validation.test.js (mirrors formula validation, incl. the
verified-url rule) and validation-negative.test.js (missing/invalid
required fields are detected).
- Add xss.test.js: builds a throwaway tap from a hostile formula and
asserts </script> breakouts, javascript: URLs, and U+2028/U+2029 are
neutralized in the output and embedded JSON.
- Add stability-parity.test.js: runs the same fixtures through the real
bash detect_stability and JS detectStability and asserts they agree,
preventing drift between the two implementations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a test that fails when a README formula/cask row is missing or its version/description drifts from the Formula/*.rb or Casks/*.rb source of truth, keeping the hand-maintained tables honest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Consolidate build/test/deploy into one workflow: the site is built and tested once, uploaded as the Pages artifact, and the deploy job (gated on both test and brew jobs succeeding, main pushes only) deploys that exact artifact. Removes the separate deploy-site.yml that could publish an untested, independently-rebuilt site. - Build runs strict in CI (CI=true), so a GitHub API failure fails the run instead of silently shipping pages without version history. - Add ShellCheck and `npm audit --audit-level=high` steps. - New macos-latest brew job: brew style, readall, audit --strict --online (formulae + casks), livecheck, a real artifact-checksum verification (scripts/verify-checksums.sh downloads each asset and compares sha256), and a formula install/test smoke check. - Pin every action to a full commit SHA with a version comment. - Add a weekly scheduled release-freshness workflow (scripts/check-release-freshness.mjs) that flags packages behind their latest GitHub release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
WalkthroughThe PR consolidates CI and Pages deployment, adds release freshness and checksum validation, server-renders documentation pages, improves search accessibility and safety, updates package metadata, and broadens automated site and package validation. ChangesSite and package validation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CI as GitHub Actions CI
participant Build as build-site.sh
participant API as GitHub Releases API
participant Pages as GitHub Pages
CI->>Build: Run strict site build
Build->>API: Fetch release history
API-->>Build: Return release metadata
Build-->>CI: Produce tested Pages artifact
CI->>Pages: Deploy artifact after test and brew jobs succeed
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
brew style <tap> also lints shell scripts under the tap with Homebrew's
shfmt/shellcheck style (prefers [[ ]] and ${var}), which failed on the build
tooling. Point brew style at the .rb files so it validates the packages it is
meant to; build/CI scripts are already covered by the ShellCheck step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- iconwolf: order symbol deps (:macos, arch:) before the named "node" dep per FormulaAudit/DependencyOrder. - wolfwave: alphabetize the zap trash array per Cask/ArrayAlphabetization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FormulaAudit/DependencyOrder wants `depends_on arch:` before `depends_on :macos`. Reorder and mirror the README template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
tests/xss.test.js (1)
50-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicates
extractJSONinstead of reusing it.This re-implements the same non-greedy
const data = ({...});extraction already exported asextractJSONinhelpers.js(which also asserts the match isn't null with a clear failure message). Reusing it here avoids two copies of the same fragile regex drifting apart.♻️ Suggested fix
-import { buildFixtureTap } from "./helpers.js"; +import { buildFixtureTap, extractJSON } from "./helpers.js"; ... - const match = html.match(/const data = (\{.*?\});/s); - expect(match).not.toBeNull(); - const data = JSON.parse(match[1]); + const data = extractJSON(html, "data");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/xss.test.js` around lines 50 - 57, Update the “keeps the embedded search index parseable as JSON” test to reuse the exported extractJSON helper from helpers.js instead of duplicating the regex extraction and null assertion; keep the existing JSON and formulae.desc assertions unchanged.tests/helpers.js (2)
28-40: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueNo cleanup of
mkdtempSyncdirectories.
runBuild,callBashFunction, andbuildFixtureTapeach create a temp directory but never remove it. Over a full CI run this can accumulate disk usage (especiallybuildFixtureTap, which recursively copiesscripts/andsite/per call). Consider anafterAll/afterEachcleanup (e.g.rmSync(dir, { recursive: true, force: true })) for at least the fixture-tap and bash-function temp dirs.Also applies to: 169-178, 185-208
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/helpers.js` around lines 28 - 40, Clean up temporary directories created by runBuild, callBashFunction, and buildFixtureTap by tracking each directory for the appropriate test scope and removing it with recursive, forced deletion in afterAll or afterEach hooks. Ensure cleanup runs even when tests fail and preserves reuse of runBuild’s cached build output within its scope.
156-162: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFragile brace-matching extraction of shared source.
Both
loadSharedFunctionandcallBashFunctionextract a function body via a non-greedy regex ending at the first\n}/\n\}at column 0. This is a reasonable approach to avoid drift fromsite/shared.js/scripts/build-site.sh, but it silently mis-extracts (truncates early) if the target function ever contains a nested block whose closing brace lands at column 0 — a subtle, hard-to-diagnose failure mode. Static analysis also flags thenew Function()eval and the non-literalRegExpconstruction; in this context the inputs are hardcoded test-call literals, not attacker-controlled, so the practical risk is low, but the extraction fragility itself is worth a short comment noting the column-0-brace assumption so future refactors ofshared.js/build-site.shdon't break it silently.Also applies to: 169-178
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/helpers.js` around lines 156 - 162, Document the extraction assumption in both loadSharedFunction and callBashFunction: their regexes require the target function’s closing brace to appear at column 0 and can truncate on an earlier matching brace. Add a concise maintenance comment near each extraction explaining this constraint and the required alignment with site/shared.js and scripts/build-site.sh, without changing the existing extraction behavior.Source: Linters/SAST tools
tests/build-output.test.js (1)
83-102: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRaw
.rbvalues compared against HTML-escaped output.
render_detail_pageinscripts/build-site.shHTML-escapesdesc,homepage, etc. before embedding them, but these tests compare the raw.rbfield value directly against the escaped HTML viatoContain. This works only while current values are free of&,<,>,". Any future desc containing one of these (e.g. "Sync & backup") will fail this assertion even though the page renders correctly.html-content.test.jsavoids this by comparing against Cheerio's decoded.text().♻️ Suggested approach
- expect(html).toContain(f.desc); + expect(html).toContain(f.desc.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"));Or better, switch these assertions to
loadHTML(...)+ Cheerio selectors like the equivalent checks intests/html-content.test.js.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/build-output.test.js` around lines 83 - 102, Update the metadata assertions in the Formula and Cask test cases to parse each generated page with loadHTML and validate decoded element text through Cheerio selectors, matching the approach in html-content.test.js. Replace raw toContain checks for escaped fields such as desc, homepage, license, and appName while preserving the existing metadata coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Formula/iconwolf.rb`:
- Around line 7-9: Reorder the dependency declarations in the formula so the
macOS and ARM64 platform constraints appear before depends_on "node". Preserve
all existing dependencies and constraints unchanged.
In `@scripts/check-release-freshness.mjs`:
- Around line 48-54: Update the release-fetch logic in the freshness-check loop
to use an explicit release-selection policy by enumerating releases when
prereleases must be considered, filtering the results accordingly, and selecting
the intended release rather than relying on /releases/latest. Add a bounded
request timeout via AbortSignal.timeout(...) to the fetch call, while preserving
the existing non-OK error handling and freshness comparison flow.
In `@scripts/verify-checksums.sh`:
- Around line 16-20: Update the checksum validation logic around the version,
URL, and SHA256 assignments in the script to use braced variable expansions and
Bash [[ ... ]] conditionals, then apply brew style --fix to resolve any
remaining formatting violations before checksum verification runs.
---
Nitpick comments:
In `@tests/build-output.test.js`:
- Around line 83-102: Update the metadata assertions in the Formula and Cask
test cases to parse each generated page with loadHTML and validate decoded
element text through Cheerio selectors, matching the approach in
html-content.test.js. Replace raw toContain checks for escaped fields such as
desc, homepage, license, and appName while preserving the existing metadata
coverage.
In `@tests/helpers.js`:
- Around line 28-40: Clean up temporary directories created by runBuild,
callBashFunction, and buildFixtureTap by tracking each directory for the
appropriate test scope and removing it with recursive, forced deletion in
afterAll or afterEach hooks. Ensure cleanup runs even when tests fail and
preserves reuse of runBuild’s cached build output within its scope.
- Around line 156-162: Document the extraction assumption in both
loadSharedFunction and callBashFunction: their regexes require the target
function’s closing brace to appear at column 0 and can truncate on an earlier
matching brace. Add a concise maintenance comment near each extraction
explaining this constraint and the required alignment with site/shared.js and
scripts/build-site.sh, without changing the existing extraction behavior.
In `@tests/xss.test.js`:
- Around line 50-57: Update the “keeps the embedded search index parseable as
JSON” test to reuse the exported extractJSON helper from helpers.js instead of
duplicating the regex extraction and null assertion; keep the existing JSON and
formulae.desc assertions unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: db35222c-81d4-4805-b1e8-5a6af5e2972e
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (22)
.github/workflows/ci.yml.github/workflows/deploy-site.yml.github/workflows/release-freshness.ymlCasks/wolfwave.rbFormula/iconwolf.rbREADME.mdpackage.jsonscripts/build-site.shscripts/check-release-freshness.mjsscripts/verify-checksums.shsite/detail-template.htmlsite/partials/search-modal.htmlsite/shared.jstests/build-output.test.jstests/cask-validation.test.jstests/helpers.jstests/html-content.test.jstests/readme-tables.test.jstests/stability-parity.test.jstests/validation-negative.test.jstests/xss.test.jsvitest.config.js
💤 Files with no reviewable changes (1)
- .github/workflows/deploy-site.yml
| const r = await fetch(`https://api.github.com/repos/${it.repo}/releases/latest`, { headers }); | ||
| if (!r.ok) { | ||
| console.log(`! ${it.name}: GitHub API ${r.status} for ${it.repo}`); | ||
| errors++; | ||
| continue; | ||
| } | ||
| const latest = norm((await r.json()).tag_name); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tracked files matching script:"
git ls-files | grep -F 'scripts/check-release-freshness.mjs' || true
if [ -f scripts/check-release-freshness.mjs ]; then
echo
echo "Outline/structure:"
ast-grep outline scripts/check-release-freshness.mjs || true
echo
echo "Relevant lines:"
cat -n scripts/check-release-freshness.mjs | sed -n '1,160p'
fi
echo
echo "Search for related config/release-freshness references:"
rg -n "check-release-freshness|release-freshness|releases/latest|AbortSignal|timeout" -S . || true
echo
echo "Node availability/version:"
node --version 2>/dev/null || trueRepository: MrDemonWolf/homebrew-den
Length of output: 4044
🌐 Web query:
GitHub REST API releases /owner/repo/releases/latest excludes prerelease documentation
💡 Result:
The GitHub REST API endpoint GET /repos/{owner}/{repo}/releases/latest specifically retrieves the most recent non-prerelease, non-draft release [1][2]. According to official GitHub documentation, this endpoint explicitly excludes prerelease and draft releases from being returned [1][3]. The release considered "latest" is determined by the created_at attribute, which corresponds to the date of the commit associated with the release, rather than the date it was published [1][2]. Additionally, GitHub documentation notes that draft and prerelease versions cannot be programmatically set as the "latest" release for a repository [3]. If a user manually marks a prerelease version as the "latest" via the GitHub web interface, it may appear as such, but the REST API's /latest endpoint is designed to filter these out in favor of the most recent stable (non-prerelease) release [1][2][4].
Citations:
- 1: https://docs.github.com/en/rest/releases/releases
- 2: https://docs.github.com/rest/releases/releases
- 3: https://docs.github.com/en/enterprise-server@3.18/rest/releases/releases
- 4: GitHub releases of pre-release versions (alpha, beta, ... ) marked as latest release version prusa3d/PrusaSlicer#9757
Make release selection explicit and bound the request.
/releases/latest only returns the newest non-draft, non-prerelease release; enumerate releases if the check should include prereleases or respect an explicit policy. Also add a timeout, e.g. AbortSignal.timeout(...), so an unreachable API does not keep the workflow waiting.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/check-release-freshness.mjs` around lines 48 - 54, Update the
release-fetch logic in the freshness-check loop to use an explicit
release-selection policy by enumerating releases when prereleases must be
considered, filtering the results accordingly, and selecting the intended
release rather than relying on /releases/latest. Add a bounded request timeout
via AbortSignal.timeout(...) to the fetch call, while preserving the existing
non-OK error handling and freshness comparison flow.
| version=$(field "$rb" version) | ||
| url=$(field "$rb" url) | ||
| sha256=$(field "$rb" sha256) | ||
| if [ -z "$url" ] || [ -z "$sha256" ]; then | ||
| echo "-- skip $(basename "$rb") (no url/sha256)" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Fix the shell style violations blocking Homebrew validation.
brew style currently fails before checksum verification runs. Brace variable expansions and use [[ ... ]]; then run brew style --fix for the remaining formatting changes.
Proposed fix
- version=$(field "$rb" version)
- url=$(field "$rb" url)
- sha256=$(field "$rb" sha256)
- if [ -z "$url" ] || [ -z "$sha256" ]; then
- echo "-- skip $(basename "$rb") (no url/sha256)"
+ version="$(field "${rb}" version)"
+ url="$(field "${rb}" url)"
+ sha256="$(field "${rb}" sha256)"
+ if [[ -z "${url}" || -z "${sha256}" ]]; then
+ echo "-- skip $(basename "${rb}") (no url/sha256)"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| version=$(field "$rb" version) | |
| url=$(field "$rb" url) | |
| sha256=$(field "$rb" sha256) | |
| if [ -z "$url" ] || [ -z "$sha256" ]; then | |
| echo "-- skip $(basename "$rb") (no url/sha256)" | |
| version="$(field "${rb}" version)" | |
| url="$(field "${rb}" url)" | |
| sha256="$(field "${rb}" sha256)" | |
| if [[ -z "${url}" || -z "${sha256}" ]]; then | |
| echo "-- skip $(basename "${rb}") (no url/sha256)" |
🧰 Tools
🪛 GitHub Actions: CI / 1_Homebrew validation.txt
[warning] 16-41: ShellCheck reported multiple style issues (SC2250 and SC2292: Prefer [[ ]] over [ ] for tests in Bash/Ksh/Busybox) in /opt/homebrew/Library/Taps/mrdemonwolf/homebrew-den/scripts/verify-checksums.sh.
🪛 GitHub Check: Homebrew validation
[failure] 19-19:
Prefer putting braces around variable references even when not strictly required.
[failure] 19-19:
Prefer [[ ]] over [ ] for tests in Bash/Ksh/Busybox.
[failure] 18-18:
Prefer putting braces around variable references even when not strictly required.
[failure] 17-17:
Prefer putting braces around variable references even when not strictly required.
[failure] 16-16:
Prefer putting braces around variable references even when not strictly required.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/verify-checksums.sh` around lines 16 - 20, Update the checksum
validation logic around the version, URL, and SHA256 assignments in the script
to use braced variable expansions and Bash [[ ... ]] conditionals, then apply
brew style --fix to resolve any remaining formatting violations before checksum
verification runs.
Sources: Linters/SAST tools, Pipeline failures
A url/sha256 nested only inside on_macos/on_arm leaves the formula with no stable url when Homebrew loads it in other OS/arch contexts, so brew readall rejects it as invalid. Since only an Apple-Silicon macOS binary is published, move url + sha256 to the top level and rely on depends_on arch: :arm64 + depends_on :macos to restrict installation. Update the README template to match (nested per-platform blocks are for formulae with multiple binaries). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Production-readiness pass over the tap: fixes shipped package bugs, hardens the
site build (security + reliability), server-renders detail pages, overhauls the
test suite, and consolidates CI so untested output can't deploy.
P0 — package correctness
(
depends_on :macos+arch: :arm64, before platform blocks); nestedon_macos/on_arm; test assertsversion.to_s; README template updated.verified:(homepage on a different host);depends_on macos: :tahoe; trim desc to <=80 chars.P1 — deps, tests, CI
npm audit= 0 vulnerabilities..rb, add caskvalidation, negative-field checks, unresolved-placeholder scan.
artifact-checksum verification, install smoke. Weekly release-freshness cron.
deploy gated on test+brew success. Actions SHA-pinned. ShellCheck + npm audit.
P1 — site security & reliability
U+2028/2029; URL scheme allowlist). Hostile-input regression tests.
v, pagination, STRICT/OFFLINE,Tailwind via
npx --no-install.P1 — dedup
.rb= source of truth; README tablesvalidated; stability rules guarded by a bash<->JS parity test.
P2 — a11y & maintainability
live-region, focus restore. Resilient clipboard/localStorage. Python 3 dep documented.
Verification
builds leave no placeholders and server-render essential content.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Updates
Bug Fixes