Skip to content

feat: add safe interactive GitHub Pages demo - #33

Merged
lsadehaan merged 3 commits into
masterfrom
feat/github-pages-demo
Aug 2, 2026
Merged

feat: add safe interactive GitHub Pages demo#33
lsadehaan merged 3 commits into
masterfrom
feat/github-pages-demo

Conversation

@lsadehaan

@lsadehaan lsadehaan commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a polished GitHub Pages showcase using the real Lit UI and TerminalClient
  • connect it to a deterministic in-browser WebSocket/protocol emulator with no backend or OS execution
  • add strict CSP, an exact artifact allowlist, responsive design, and accessible status/input semantics
  • add protocol unit tests plus black-box Pages E2E coverage in Chromium, Firefox, and WebKit
  • add a least-privilege SHA-pinned Pages deployment with commit-specific live verification
  • align checkout and setup-node actions on their latest stable pinned releases

Safety boundary

The demo accepts only demo://terminal, uses no native WebSocket, fetch, XHR, eval, child process, PTY, or Docker API, and ships with connect-src none. Unsupported commands are rejected by an allowlist interpreter.

Validation

  • npm run validate: 288 tests, coverage policy, CRAP report and ratchet, formatting, lint, typecheck, docs, and dependency checks pass
  • npm run build and npm run pages:check pass; generated site is 5 allowlisted files / about 461 KB
  • npm run test:e2e:browser: 27/27 pass across Chromium, Firefox, and WebKit, covering both the existing real PTY fixture and the static demo
  • three additional isolated WebKit cold starts pass
  • Claude Code with Fable completed a read-only review with no high or critical findings; all substantive low-level findings were addressed

Deployment

Pages is configured for GitHub Actions with a master-only environment policy. After merge, the workflow publishes https://www.idnteq.net/lit-shell/ from the reviewed artifact and verifies the exact deployed commit SHA.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@lsadehaan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d7f73a00-3426-48a3-89d2-6263fa7e246d

📥 Commits

Reviewing files that changed from the base of the PR and between 4778350 and a3dc4f1.

📒 Files selected for processing (5)
  • .github/workflows/pages.yml
  • README.md
  • tests/browser/pages-demo.spec.ts
  • tests/fixtures/browser/lit-shell-fixture.ts
  • tests/fixtures/browser/pages-demo-fixture.ts
📝 Walkthrough

Walkthrough

The PR adds a zero-network browser terminal demo, static Pages artifact generation and validation, browser and unit tests, Pages deployment, and updated CI action pins.

Changes

Pages demo

Layer / File(s) Summary
Demo transport and protocol
demo/demo-websocket.ts, tests/demo-websocket.test.ts
Adds the deterministic shell simulator, WebSocket-compatible protocol, session lifecycle, terminal editing, and unit tests.
Demo page and browser bootstrap
demo/index.html, demo/app.ts, demo/style.css, README.md, eslint.config.mjs, knip.json, tsconfig.json
Adds the static page, security metadata, responsive styling, browser startup logic, accessibility state, documentation, and TypeScript tooling.
Pages artifact build and validation
scripts/build-pages.js, scripts/check-pages.js, package.json, .gitignore, .markdownlint-cli2.mjs, .prettierignore
Builds a revisioned _site artifact and validates its files, content, bundle, security markers, and size.
Browser fixture and E2E coverage
tests/fixtures/browser/pages-demo-fixture.ts, tests/browser/pages-demo.spec.ts
Serves the artifact through a restricted local fixture and tests loading, network isolation, terminal behavior, accessibility, CSP, and responsive layout.
Pages deployment and CI integration
.github/workflows/pages.yml, .github/workflows/ci.yml, .github/workflows/codeql.yml, .github/workflows/dependency-review.yml, .github/workflows/release.yml
Adds Pages build, deployment, and live verification jobs. CI runs artifact checks, and workflow action pins move to the newer revisions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant PagesBuild
  participant PagesArtifact
  participant GitHubPages
  participant LiveVerifier
  GitHubActions->>PagesBuild: Build demo artifact
  PagesBuild->>PagesArtifact: Generate and validate _site
  PagesBuild->>GitHubPages: Upload Pages artifact
  GitHubPages-->>GitHubActions: Deployment URL
  LiveVerifier->>GitHubPages: Fetch deployed page
  GitHubPages-->>LiveVerifier: Page and revision content
  LiveVerifier->>GitHubPages: Fetch deployed JavaScript bundle
  GitHubPages-->>LiveVerifier: Bundle content
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding a safe interactive GitHub Pages demo.
Description check ✅ Passed The description covers the demo, safety model, validation, deployment, testing, and security details, but omits the template checklist sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/github-pages-demo

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread tests/fixtures/browser/pages-demo-fixture.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
.github/workflows/pages.yml (1)

36-37: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

Bootstrap npm from a reviewed, integrity-locked source.

Line 37 installs the deployment package manager outside a committed lockfile. This makes the Pages build depend on a registry-resolved toolchain that is not fully reviewed with the repository dependencies.

Use a committed bootstrap lockfile or a verified npm artifact. Apply the same bootstrap pattern to the other workflows.

🤖 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 @.github/workflows/pages.yml around lines 36 - 37, Replace the unpinned
registry installation in the “Install the repository npm version” workflow step
with a committed, integrity-locked bootstrap lockfile or verified npm artifact.
Apply the same locked bootstrap approach to npm setup steps in the other
workflows, preserving the required npm version.

Source: Linters/SAST tools

scripts/check-pages.js (1)

25-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The symlink assertion is unreachable.

lstat does not follow symlinks. For a symlink, fileStats.isFile() returns false, so line 28 throws first with the message must be a regular file. Line 29 can never fail. A symlink is still rejected, so the artifact stays safe, but the error message misidentifies the cause. Order the checks so the symlink case reports itself.

♻️ Proposed change
   const fileStats = await lstat(filePath);
-  assert(fileStats.isFile(), `${filename} must be a regular file`);
   assert(!fileStats.isSymbolicLink(), `${filename} must not be a symlink`);
+  assert(fileStats.isFile(), `${filename} must be a regular file`);
   totalBytes += (await stat(filePath)).size;
🤖 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-pages.js` around lines 25 - 31, Reorder the assertions in the
actualFiles loop so fileStats.isSymbolicLink() is checked before
fileStats.isFile(). Preserve both validations and ensure symlinks report “must
not be a symlink” while other non-regular entries report “must be a regular
file.”
scripts/build-pages.js (1)

19-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use replaceAll for the build placeholder.

replace with a string pattern substitutes only the first occurrence. If demo/index.html ever contains __LIT_SHELL_BUILD__ twice, the artifact keeps an unresolved placeholder and scripts/check-pages.js fails the build at line 55. replaceAll removes that failure mode and keeps the missing-placeholder guard intact.

♻️ Proposed change
-const renderedHtml = sourceHtml.replace('__LIT_SHELL_BUILD__', buildRevision);
+const renderedHtml = sourceHtml.replaceAll(
+  '__LIT_SHELL_BUILD__',
+  buildRevision,
+);
🤖 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/build-pages.js` around lines 19 - 22, Update the replacement in the
rendered HTML generation flow to use replaceAll for every __LIT_SHELL_BUILD__
occurrence, while preserving the existing renderedHtml === sourceHtml guard for
detecting a missing placeholder.
tests/browser/pages-demo.spec.ts (1)

11-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard afterAll against a failed beforeAll.

If startPagesDemoFixture rejects, for example when _site is missing, fixture stays undefined. afterAll then throws TypeError: Cannot read properties of undefined, and that error replaces the actionable message from the fixture in the report.

♻️ Proposed change
 test.afterAll(async () => {
-  await fixture.close();
+  await fixture?.close();
 });

Declare the variable accordingly:

-let fixture: PagesDemoFixture;
+let fixture: PagesDemoFixture | undefined;
🤖 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/browser/pages-demo.spec.ts` around lines 11 - 17, Update the fixture
variable used by the beforeAll and afterAll hooks to allow an unset value, and
guard fixture.close() in afterAll so cleanup runs only when
startPagesDemoFixture successfully assigned a fixture. Preserve the original
fixture-startup error when initialization fails.
demo/demo-websocket.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export DEMO_SHELL/DEMO_CWD instead of duplicating the literals in demo/app.ts. The shell path and working directory are defined once in demo/demo-websocket.ts but re-typed as separate string literals in demo/app.ts. A future edit to one side without the other would silently break session validation (mismatched shell/cwd between the client's spawn() call and the server-side constants).

  • demo/demo-websocket.ts#L12-13: export DEMO_SHELL and DEMO_CWD alongside DEMO_WEBSOCKET_URL.
  • demo/app.ts#L36-37: import and use the exported DEMO_SHELL/DEMO_CWD instead of the literals '/bin/lit-shell-demo' and '/demo'.
  • demo/app.ts#L55-56: use the same imported constants in the spawn() options instead of re-typing the literals.
🔧 Proposed fix
-const SESSION_ID = 'browser-demo-session';
-const DEMO_SHELL = '/bin/lit-shell-demo';
-const DEMO_CWD = '/demo';
+const SESSION_ID = 'browser-demo-session';
+export const DEMO_SHELL = '/bin/lit-shell-demo';
+export const DEMO_CWD = '/demo';
-import { DEMO_WEBSOCKET_URL, installDemoWebSocket } from './demo-websocket.js';
+import {
+  DEMO_CWD,
+  DEMO_SHELL,
+  DEMO_WEBSOCKET_URL,
+  installDemoWebSocket,
+} from './demo-websocket.js';
...
-  terminal.shell = '/bin/lit-shell-demo';
-  terminal.cwd = '/demo';
+  terminal.shell = DEMO_SHELL;
+  terminal.cwd = DEMO_CWD;
...
   await terminal.spawn({
-    shell: '/bin/lit-shell-demo',
-    cwd: '/demo',
+    shell: DEMO_SHELL,
+    cwd: DEMO_CWD,
     allowJoin: false,
     enableHistory: false,
   });
🤖 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 `@demo/demo-websocket.ts` at line 1, Export DEMO_SHELL and DEMO_CWD from the
constants section alongside DEMO_WEBSOCKET_URL in demo-websocket.ts, then import
and reuse them throughout app.ts for session validation and spawn() options
instead of duplicating the shell path and working-directory literals.
demo/index.html (1)

5-8: 🔒 Security & Privacy | 🔵 Trivial

No clickjacking protection; likely unfixable within GitHub Pages constraints.

The CSP has no frame-ancestors directive, so the page can be embedded in a hostile iframe. Note that frame-ancestors is not enforceable when delivered via a <meta> tag per the CSP spec, and X-Frame-Options cannot be set as an HTTP header on GitHub Pages without a fronting proxy. If clickjacking protection becomes a requirement later, front the deployment with a service that can set response headers (e.g., Cloudflare Pages or a CDN in front of GitHub Pages).

🤖 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 `@demo/index.html` around lines 5 - 8, No code change is required in the
demo/index.html CSP: frame-ancestors is ineffective in a meta tag, and GitHub
Pages cannot provide the required X-Frame-Options or CSP response headers.
Document this deployment limitation only if needed, and use a fronting service
with configurable response headers if clickjacking protection becomes mandatory.
🤖 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 `@demo/demo-websocket.ts`:
- Around line 431-443: Update DemoWebSocket.send so it throws InvalidStateError
only when readyState is CONNECTING; for CLOSING or CLOSED, discard the data and
return before validating or enqueueing the handleClientMessage microtask, while
preserving the existing OPEN behavior.

In `@demo/style.css`:
- Line 14: Update the Stylelint configuration to enable camelCase SVG-derived
keywords via the value-keyword-case option’s camelCaseSvgKeywords setting, so
the optimizeLegibility declaration in the stylesheet passes linting without
changing its casing.

In `@tests/fixtures/browser/pages-demo-fixture.ts`:
- Around line 105-110: Update the catch handler in the request handler to guard
writeHead with a response.headersSent check, avoiding header writes after
response.end has begun. Replace the raw error response with a fixed generic body
and log the caught error via console.error for diagnostics.
- Around line 130-134: Update closeServer to call server.closeAllConnections()
immediately after server.close() and before awaiting the 'close' event, ensuring
idle keep-alive connections are terminated while preserving the existing
listening guard.

---

Nitpick comments:
In @.github/workflows/pages.yml:
- Around line 36-37: Replace the unpinned registry installation in the “Install
the repository npm version” workflow step with a committed, integrity-locked
bootstrap lockfile or verified npm artifact. Apply the same locked bootstrap
approach to npm setup steps in the other workflows, preserving the required npm
version.

In `@demo/demo-websocket.ts`:
- Line 1: Export DEMO_SHELL and DEMO_CWD from the constants section alongside
DEMO_WEBSOCKET_URL in demo-websocket.ts, then import and reuse them throughout
app.ts for session validation and spawn() options instead of duplicating the
shell path and working-directory literals.

In `@demo/index.html`:
- Around line 5-8: No code change is required in the demo/index.html CSP:
frame-ancestors is ineffective in a meta tag, and GitHub Pages cannot provide
the required X-Frame-Options or CSP response headers. Document this deployment
limitation only if needed, and use a fronting service with configurable response
headers if clickjacking protection becomes mandatory.

In `@scripts/build-pages.js`:
- Around line 19-22: Update the replacement in the rendered HTML generation flow
to use replaceAll for every __LIT_SHELL_BUILD__ occurrence, while preserving the
existing renderedHtml === sourceHtml guard for detecting a missing placeholder.

In `@scripts/check-pages.js`:
- Around line 25-31: Reorder the assertions in the actualFiles loop so
fileStats.isSymbolicLink() is checked before fileStats.isFile(). Preserve both
validations and ensure symlinks report “must not be a symlink” while other
non-regular entries report “must be a regular file.”

In `@tests/browser/pages-demo.spec.ts`:
- Around line 11-17: Update the fixture variable used by the beforeAll and
afterAll hooks to allow an unset value, and guard fixture.close() in afterAll so
cleanup runs only when startPagesDemoFixture successfully assigned a fixture.
Preserve the original fixture-startup error when initialization fails.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 766a3c6c-7b9f-47dd-9412-b6dc8ab03a39

📥 Commits

Reviewing files that changed from the base of the PR and between a832419 and 4778350.

⛔ Files ignored due to path filters (1)
  • demo/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (22)
  • .github/workflows/ci.yml
  • .github/workflows/codeql.yml
  • .github/workflows/dependency-review.yml
  • .github/workflows/pages.yml
  • .github/workflows/release.yml
  • .gitignore
  • .markdownlint-cli2.mjs
  • .prettierignore
  • README.md
  • demo/app.ts
  • demo/demo-websocket.ts
  • demo/index.html
  • demo/style.css
  • eslint.config.mjs
  • knip.json
  • package.json
  • scripts/build-pages.js
  • scripts/check-pages.js
  • tests/browser/pages-demo.spec.ts
  • tests/demo-websocket.test.ts
  • tests/fixtures/browser/pages-demo-fixture.ts
  • tsconfig.json

Comment thread demo/demo-websocket.ts
Comment on lines +431 to +443
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {
if (this.readyState !== DemoWebSocket.OPEN) {
throw new DOMException('WebSocket is not open', 'InvalidStateError');
}
if (typeof data !== 'string') {
throw new TypeError('The demo protocol accepts text messages only');
}

queueMicrotask(() => {
if (this.readyState === DemoWebSocket.OPEN)
this.handleClientMessage(data);
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '400,465p' demo/demo-websocket.ts

echo "== ws.send usages in repo =="
rg -n "\.send\(" --glob '*.{ts,js,mjs,mts}' . | head -200

echo "== WebSocket declarations/usages =="
rg -n "DemoWebSocket|implements WebSocket|readyState|CONNECTING|CLOSING|CLOSED" --glob '*.{ts,js,mjs,mts}' . | head -200

echo "== package scripts/tests =="
if [ -f package.json ]; then cat package.json; fi

Repository: lsadehaan/lit-shell

Length of output: 22586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== browser fixture send-related excerpt =="
sed -n '120,175p' tests/fixtures/browser/lit-shell-fixture.ts

echo "== package-lock relevant DemoWebSocket install =="
node - <<'JS'
const fs = require('fs');
const p = 'package-lock.json';
if (!fs.existsSync(p)) { console.log('no package-lock.json'); process.exit(0); }
const lock = JSON.parse(fs.readFileSync(p, 'utf8'));
function find(name, path='node_modules') {
  for (const k of Object.keys(lock.packages || {})) {
    if (k.endsWith('node_modules/lit-shell') || k.includes(name)) {
      console.log(k, lock.packages[k].version);
    }
  }
}
find('tests'); find('demo');
JS

echo "== send type/source in Node/typescript lib =="
rg -n "interface WebSocket|send\\(" node_modules/@types/node lib.dom.d.ts 2>/dev/null | head -100 || true

echo "== inspect lock commit/tag evidence if present =="
sed -n '165,230p' package-lock.json | grep -n "d06368b91b4382266881968086b9dc65b1cb40\|lit-shell" || true

Repository: lsadehaan/lit-shell

Length of output: 5649


🌐 Web query:

WHATWG WebSocket API send CLOSING CLOSED InvalidStateError discard data

💡 Result:

According to the WHATWG WebSockets Standard, the WebSocket.send method behavior regarding readyState is as follows: 1. InvalidStateError: The send method throws an InvalidStateError DOMException only if the WebSocket connection's readyState is CONNECTING [1][2]. 2. CLOSING and CLOSED states: If the readyState is CLOSING or CLOSED, the send method does not throw an exception; instead, it silently discards the data [3][4][5]. The specification dictates that the transmission steps only proceed if the connection is established and the closing handshake has not yet started [1][2]. Consequently, if the connection is in the process of closing or is already closed, the request to send data is ignored by the user agent [3][6].

Citations:


Do not throw from send() in CLOSING or CLOSED.

DemoWebSocket implements the browser WebSocket interface, but send() currently throws for every non-OPEN state. The WebSocket sending steps only throw when readyState is CONNECTING; for CLOSING and CLOSED, they discard the request. Match this behavior before enqueueing the handleClientMessage microtask.

🤖 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 `@demo/demo-websocket.ts` around lines 431 - 443, Update DemoWebSocket.send so
it throws InvalidStateError only when readyState is CONNECTING; for CLOSING or
CLOSED, discard the data and return before validating or enqueueing the
handleClientMessage microtask, while preserving the existing OPEN behavior.

Comment thread demo/style.css
color: #e7edf4;
background: #071019;
font-synthesis: none;
text-rendering: optimizeLegibility;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint's value-keyword-case flags optimizeLegibility.

With the default camelCaseSvgKeywords: false, Stylelint's value-keyword-case rule requires optimizelegibility (lowercase) unless the rule option is explicitly enabled for camelCase SVG-derived keywords. Confirm the project's Stylelint config already sets camelCaseSvgKeywords: true; if not, this line fails the code-quality check referenced in the PR objectives.

🔧 One way to resolve this without changing the keyword casing
+  /* stylelint-disable-next-line value-keyword-case -- optimizeLegibility is the conventional SVG-derived casing */
   text-rendering: optimizeLegibility;
📝 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.

Suggested change
text-rendering: optimizeLegibility;
/* stylelint-disable-next-line value-keyword-case -- optimizeLegibility is the conventional SVG-derived casing */
text-rendering: optimizeLegibility;
🧰 Tools
🪛 Stylelint (17.14.1)

[error] 14-14: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)

(value-keyword-case)

🤖 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 `@demo/style.css` at line 14, Update the Stylelint configuration to enable
camelCase SVG-derived keywords via the value-keyword-case option’s
camelCaseSvgKeywords setting, so the optimizeLegibility declaration in the
stylesheet passes linting without changing its casing.

Source: Linters/SAST tools

Comment thread tests/fixtures/browser/pages-demo-fixture.ts
Comment on lines +130 to +134
async function closeServer(server: Server): Promise<void> {
if (!server.listening) return;
server.close();
await once(server, 'close');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package files =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' || true

echo
echo "== engines declarations =="
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').rglob('package.json'):
    if any(part in {'.git','node_modules','dist','build','.turbo'} for part in p.parts):
        continue
    data=json.loads(p.read_text())
    print(p)
    for k in ('engines','volta'):
        if k in data: print(' ', k, ':', data[k])
PY

echo
echo "== fixture lines =="
sed -n '1,190p' tests/fixtures/browser/pages-demo-fixture.ts 2>/dev/null || true

echo
echo "== dependency mentions for Playwright/Node =="
rg -n '"playwright"|playwright|node@|>=18|engines:|typescript' package.json tests pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: lsadehaan/lit-shell

Length of output: 16783


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package files =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' || true

echo
echo "== engines declarations =="
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').rglob('package.json'):
    if any(part in {'.git','node_modules','dist','build','.turbo'} for part in p.parts):
        continue
    data=json.loads(p.read_text())
    print(p)
    for k in ('engines','volta'):
        if k in data:
            print(' ', k, ':', data[k])
PY

echo
echo "== fixture lines =="
sed -n '1,190p' tests/fixtures/browser/pages-demo-fixture.ts 2>/dev/null || true

echo
echo "== dependency mentions for Playwright/Node =="
rg -n '"playwright"|playwright|node@|>=18|engines:|typescript' package.json tests pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: lsadehaan/lit-shell

Length of output: 16783


🌐 Web query:

Playwright browser tests keep alive connections closeAllConnections documentation

💡 Result:

There is no method named closeAllConnections within the official Playwright public API [1][2][3]. The confusion likely stems from the Node.js http.Server.prototype.closeAllConnections method, which is occasionally used internally by Playwright's test infrastructure [4][5] or misidentified by users encountering errors in their own code [4]. Key distinctions regarding connection management in Playwright: 1. Node.js closeAllConnections: This is a native Node.js method added in v18.2.0 [4][6]. It is intended to forcefully close all established HTTP(S) connections to a server [7][8]. If you see this method mentioned in the context of Playwright, it is typically referring to Playwright's internal test harness or a custom server implementation, not a public Playwright API [4][5]. 2. Playwright Browser Lifecycle: To manage browser connections gracefully in Playwright, you should use official API methods: - browserContext.close: Closes a specific context and its pages [1][2]. - browser.close: Closes the entire browser instance [1][2]. It is best practice to close individual contexts first to ensure artifacts (like HARs or videos) are flushed [1][2]. 3. Keep-Alive and Connection Management: - Playwright's APIRequestContext: By default, it uses the Node.js HTTP agent. If you are experiencing connection issues or need persistent connections, you may need to configure the underlying HTTP agent (e.g., using libraries like hpagent) [9]. - Request Headers: If a server requires keep-alive connections, you can explicitly add the Connection: keep-alive header to your request configuration [10]. If you are encountering a "closeAllConnections is not a function" error, verify that you are not mistakenly calling this method on a Playwright object when you intended to use a native Node.js http.Server instance, and ensure your Node.js version is compatible if you are using that native method [4].

Citations:


Close idle keep-alive sockets before waiting for server close.

server.close() stops accepting new connections, but await once(server, 'close') still waits for existing connections to drain. If a Playwright browser keeps the fixture server alive after teardown, add server.closeAllConnections() before awaiting the server’s 'close' event. closeAllConnections() is supported in this project’s declared Node engine range.

🤖 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/fixtures/browser/pages-demo-fixture.ts` around lines 130 - 134, Update
closeServer to call server.closeAllConnections() immediately after
server.close() and before awaiting the 'close' event, ensuring idle keep-alive
connections are terminated while preserving the existing listening guard.

@lsadehaan
lsadehaan merged commit 85b8621 into master Aug 2, 2026
21 checks passed
@lsadehaan
lsadehaan deleted the feat/github-pages-demo branch August 2, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants