Skip to content

v1.2.0 #181

Open
NellowTCS wants to merge 4 commits into
mainfrom
v1.2.0
Open

v1.2.0 #181
NellowTCS wants to merge 4 commits into
mainfrom
v1.2.0

Conversation

@NellowTCS
Copy link
Copy Markdown
Member

@NellowTCS NellowTCS commented May 12, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added single-file standalone HTML build generation capability.
    • Enhanced state persistence with improved session recovery.
  • Refactor

    • Migrated build toolchain from Webpack to Vite for improved performance.
    • Restructured state management architecture.
    • Updated GitHub Actions workflows for automated deployments.
  • Documentation

    • Updated setup and development guides in README.

Comment on lines +14 to +41
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: "22" # Using latest Node 22 for 2026 compatibility

- name: Install dependencies
working-directory: Build
run: npm i

- name: Build
working-directory: Build
run: npm run build:single

- name: Rename for distribution
working-directory: Build/dist
run: mv index.html HTMLRunner.html

- name: Upload Build Artifact
uses: actions/upload-artifact@v4
with:
name: HTMLRunner-Standalone
path: Build/dist/HTMLRunner.html
if-no-files-found: error
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

This PR modernizes HTMLRunner's build toolchain from Webpack to Vite, replaces localStorage-based state management with a signal-driven reactive architecture, removes the offline-first service-worker strategy, reorganizes the application entry point to use TypeScript modules, and updates GitHub Actions workflows to support both standard and single-file artifact builds.

Changes

Build System & Architecture Modernization

Layer / File(s) Summary
Vite build configuration and toolchain
Build/vite.config.mjs, Build/package.json, Build/tsconfig.json, Build/webpack.config.js
Vite replaces Webpack as the primary bundler. vite.config.mjs introduces PWA and single-file plugins (conditional on env), inlines SVG favicons via a custom plugin, and configures workbox caching. package.json switches from webpack/webpack-cli/webpack-dev-server to vite/vite-plugin-pwa/vite-plugin-singlefile, adds Font Awesome and @nisoku/sairin dependencies, and exports build:single script for standalone HTML output. tsconfig.json is cleared (Vite uses its own config), and webpack.config.js is removed entirely.
Reactive state architecture
Build/src/appState.ts, Build/src/types.ts, Build/src/defaultContent.ts, Build/src/state.ts
State management shifts from a single module-level state object with imperative saveState()/loadState() to reactive signals (htmlState, cssState, jsState, activeTabState, activeOutputState, splitSizesState, darkModeState, autoRunState) backed by an effect that automatically persists to localStorage. appState.ts introduces createStateSnapshot(), applyStateSnapshot(), and loadState()/resetCode() functions with type-safe validation. State interface gains darkMode and autoRun fields. defaultContent.ts exports demo template strings. The old state.ts module is removed.
Application entry and module bootstrap
Build/index.html, Build/src/main.ts, Build/src/global.d.ts, Build/src/index.html
Build/index.html now loads the stylesheet from ./styles/styles.css and initializes the app via <script type="module" src="./src/main.ts"></script>, replacing inline PWA/service-worker registration. Build/src/main.ts imports FontAwesome globally, loads state on DOMContentLoaded via loadState(), and uses dynamic imports for Prettier and ZIP libraries (lazy-loading heavy dependencies). global.d.ts declares ambient types for CSS module imports including Font Awesome. Build/src/index.html (the old source file) is removed.
Editor and UI signal integration
Build/src/editor.ts, Build/src/ui.ts, Build/src/runner.ts
editor.ts now uses shared darkModeState/autoRunState and per-editor contentState signals (from appState); setDarkMode()/setAutoRun() update signals instead of local variables. Doc changes persist to contentState and optionally trigger runCode via debounce if auto-run is enabled. ui.ts refactors switchTab(), switchOutput(), toggleAutoRun(), and toggleDarkMode() to update reactive stores instead of module-level state; setPageDarkMode() no longer writes to localStorage. runner.ts lazy-loads Prettier via loadPrettierBundle() and removes saveState() calls from runCode() and formatCode(); persistence is now handled only by the appState effect.
GitHub Actions workflows and artifact handling
.github/workflows/single-file.yml, .github/workflows/static.yml, Build/pwa-assets.config.js, Build/Buildscripts/*
New workflow .github/workflows/single-file.yml runs npm run build:single, renames output to HTMLRunner.html, and uploads as the HTMLRunner-Standalone artifact. Existing .github/workflows/static.yml is rewritten to use peaceiris/actions-gh-pages@v4 for direct Build/dist publishing instead of the Pages artifact/deploy action pattern. pwa-assets.config.js configures @vite-pwa/assets-generator for Apple and maskable icon generation. Build shell scripts (build-all.sh, build.sh, build-inline.sh) are removed; npm scripts in package.json now drive the build.
PWA and styling removal
styles.css, service-worker.js, manifest.json
The offline-first service-worker strategy and custom PWA manifest are removed entirely. The large styles.css file (559 lines of layout, theme, editor, and utility styles) is deleted; styling is now integrated via Vite imports and the FontAwesome stylesheet. service-worker.js caching logic is removed. The PWA manifest is cleared (Vite PWA plugin auto-generates one).
Documentation and roadmap
README.md, TODO.md
README.md is updated to document the Vite-based toolchain, CodeMirror 6 integration, and the new npm run build:single standalone build flow; Getting Started and Dependencies sections reflect the modernized stack. License changes from GNU GPL v3.0 to MIT. TODO.md is populated with a feature checklist covering LightningFS, multi-project support, collaboration, plugins, and version control.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant index.html
    participant main.ts
    participant appState
    participant localStorage
    participant editor.ts
    participant ui.ts

    User->>Browser: Load HTMLRunner
    Browser->>index.html: Parse & render
    index.html->>main.ts: Load ESM module
    
    activate main.ts
    main.ts->>appState: loadState()
    activate appState
    appState->>localStorage: Read persisted state
    localStorage-->>appState: JSON snapshot (or null)
    appState->>appState: Validate & apply snapshot to signals
    appState-->>main.ts: State hydrated, signals updated
    deactivate appState
    
    main.ts->>editor.ts: Initialize editors with htmlState/cssState/jsState
    editor.ts->>editor.ts: Seed each editor from signal values
    
    main.ts->>ui.ts: Apply darkModeState & autoRunState to UI
    ui.ts->>ui.ts: Update theme class, toggle icons, configure listeners
    
    main.ts->>main.ts: runCode() on initial load
    deactivate main.ts
    
    User->>editor.ts: Edit HTML/CSS/JS
    editor.ts->>appState: Update contentState signal
    activate appState
    appState->>appState: Trigger persistence effect
    appState->>localStorage: Persist updated state
    deactivate appState
    
    alt autoRunState enabled
        editor.ts->>main.ts: Debounced runCode()
        main.ts->>Browser: Execute user code in iframe
    end
    
    User->>ui.ts: Toggle dark mode
    ui.ts->>appState: Update darkModeState signal
    activate appState
    appState->>localStorage: Persist setting
    deactivate appState
    ui.ts->>Browser: Apply theme class
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 From Webpack's chains to Vite's swift dance,
Signals now hum where state once stood,
No service-worker ghosts remain—
Just fresh localStorage and signals so good!
HTMLRunner hops into the modern day.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'v1.2.0' is vague and does not convey meaningful information about the changeset beyond a version number. Consider using a descriptive title that summarizes the main change, such as 'Migrate from Webpack to Vite build system' or 'Switch to Vite with PWA and single-file build support'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v1.2.0

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (1)
.github/workflows/single-file.yml (1)

13-41: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add explicit permissions block to the workflow.

The workflow does not specify permissions for GITHUB_TOKEN. As a security best practice, explicitly declare the minimum required permissions.

🔒 Proposed fix to add permissions
 jobs:
   deploy:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:

As per coding guidelines, the CodeQL static analysis tool recommends setting an explicit permissions block with contents: read as a minimal starting point.

🤖 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/single-file.yml around lines 13 - 41, The workflow lacks
an explicit permissions block for GITHUB_TOKEN; add a minimal permissions
declaration (e.g., permissions: contents: read) either at the workflow root or
scoped to the deploy job to limit token access; update the top-level or the
deploy job (job name "deploy") to include this permissions entry so the
GITHUB_TOKEN has only the required read access.
🧹 Nitpick comments (1)
.github/workflows/single-file.yml (1)

22-22: 💤 Low value

Inconsistent Node.js version across workflows.

This workflow uses Node.js 22 while .github/workflows/static.yml uses Node.js 20. Unless there's a specific reason for this difference, consider standardizing on a single Node.js version for consistency and easier maintenance.

🤖 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/single-file.yml at line 22, The workflows are using
different Node.js versions (node-version: "22" in this workflow vs node-version:
"20" in the other workflow); pick one Node.js version to standardize on (e.g.,
"20" or "22") and update the node-version key in this workflow to match the
version used in the other workflow (change node-version: "22" to the chosen
version), and then run CI locally or via GitHub Actions to confirm no
compatibility issues; also search for any other occurrences of node-version in
workflow files and align them to the same value.
🤖 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 @.github/workflows/single-file.yml:
- Around line 8-10: The workflow's concurrency stanza currently uses group:
"pages", which collides with the static.yml workflow; update the concurrency
group value (the concurrency block and its group key) in the single-file
workflow to a unique name such as "single-file-build" (keep cancel-in-progress
as-is) so this workflow no longer blocks or is blocked by the Pages workflow.

In `@Build/pwa-assets.config.js`:
- Around line 1-27: The dependency constraint for `@vite-pwa/assets-generator` is
incorrect (1.0.1 does not exist); update the package version in your dependency
manifest so installs resolve deterministically — change the version specifier
from ^1.0.1 to ^1.0.2 (or to ^1.0.0 if you intend compatibility with the initial
1.0.0 release). Ensure the updated version is saved in package.json and any
lockfile is refreshed so the build uses the published release compatible with
the existing usage of defineConfig and minimal2023Preset.

In `@Build/src/appState.ts`:
- Around line 82-88: The code currently only type-checks
snapshot.activeTab/activeOutput before calling
activeTabState.set/activeOutputState.set, which lets invalid persisted strings
propagate; update this to whitelist values: define or import the canonical
allowed sets (e.g., TAB_KEYS or an enum used by the UI/editor and OUTPUT_KEYS)
and verify snapshot.activeTab and snapshot.activeOutput are members (e.g.,
allowedTabs.includes(snapshot.activeTab)) before calling
activeTabState.set/activeOutputState.set; if a value is invalid, skip setting
(or set a safe default) and optionally log a warning so downstream lookups
remain stable.
- Around line 48-54: The effect currently calls
localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()))
on every content mutation, causing sync hot-path writes; wrap this persistence
in a debounce/throttle so writes occur at a limited frequency (e.g., debounce
250–1000ms) or use requestIdleCallback to perform the write when idle, and
optionally skip writes if the new snapshot equals the last persisted snapshot.
Update the effect that references stateHydrated.get(), createStateSnapshot(),
and the localStorage.setItem call to schedule the serialized write via the
chosen debounce/throttle/idle mechanism and cancel any pending timer on cleanup.

In `@Build/src/defaultContent.ts`:
- Line 8: The default HTML in Build/src/defaultContent.ts references <script
src="main.js"></script> but the build/export emits "script.js", so update the
script tag in the default export to <script src="script.js"></script> (i.e.,
replace "main.js" with "script.js" in the default content string) so the
exported project loads the correct JS file out of the box.

In `@Build/src/runner.ts`:
- Around line 17-33: The cached promise prettierBundlePromise currently holds a
rejected promise if the dynamic imports fail, causing all future format attempts
to fail; modify the initialization in the import block so the
Promise.all(...).then(...) chain also has a .catch(err => {
prettierBundlePromise = undefined; throw err; }) (or similar reset) so that on
load failure the cached prettierBundlePromise is cleared and subsequent calls
retry the dynamic imports; reference the prettierBundlePromise variable and the
Promise.all([... import(...) ...]).then(...) chain in runner.ts when applying
this change.

In `@Build/vite.config.mjs`:
- Around line 7-25: The plugin inlineSvgFaviconPlugin is treating the file at
options.svg as UTF-8 SVG (transformIndexHtml reads it as text and runs
XML/whitespace regex) but the project supplies a PNG, corrupting the favicon;
fix by either (A) replacing the PNG with a real SVG and updating the favicon
reference to public/favicon.svg so inlineSvgFaviconPlugin can continue to read
UTF-8 SVG, or (B) update inlineSvgFaviconPlugin/transformIndexHtml to detect the
file type from options.svg (e.g., by extension or mime sniff), read binary for
non-SVG files (fs.readFileSync(options.svg) without "utf8"), skip the
XML/whitespace regex for binary images, base64-encode the raw buffer, and set
the correct type in faviconTag (image/png vs image/svg+xml) before inserting
into the head.

In `@TODO.md`:
- Line 12: Update the Auto Completion checklist item text to hyphenate
“built-in” in the phrase inside the markdown list item (the line containing "- [
] **Auto Completion** (using the built in CodeMirror tools)"); change "built in"
to "built-in" so the item reads "(using the built-in CodeMirror tools)".

---

Duplicate comments:
In @.github/workflows/single-file.yml:
- Around line 13-41: The workflow lacks an explicit permissions block for
GITHUB_TOKEN; add a minimal permissions declaration (e.g., permissions:
contents: read) either at the workflow root or scoped to the deploy job to limit
token access; update the top-level or the deploy job (job name "deploy") to
include this permissions entry so the GITHUB_TOKEN has only the required read
access.

---

Nitpick comments:
In @.github/workflows/single-file.yml:
- Line 22: The workflows are using different Node.js versions (node-version:
"22" in this workflow vs node-version: "20" in the other workflow); pick one
Node.js version to standardize on (e.g., "20" or "22") and update the
node-version key in this workflow to match the version used in the other
workflow (change node-version: "22" to the chosen version), and then run CI
locally or via GitHub Actions to confirm no compatibility issues; also search
for any other occurrences of node-version in workflow files and align them to
the same value.
🪄 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

Run ID: d116896b-483e-43ba-b136-24f76242b42e

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4f534 and 431f6e1.

⛔ Files ignored due to path filters (2)
  • Build/package-lock.json is excluded by !**/package-lock.json
  • Build/public/favicon.png is excluded by !**/*.png
📒 Files selected for processing (28)
  • .github/workflows/single-file.yml
  • .github/workflows/static.yml
  • Build/Buildscripts/build-all.sh
  • Build/Buildscripts/build-inline.sh
  • Build/Buildscripts/build.sh
  • Build/index.html
  • Build/package.json
  • Build/pwa-assets.config.js
  • Build/src/appState.ts
  • Build/src/defaultContent.ts
  • Build/src/editor.ts
  • Build/src/global.d.ts
  • Build/src/index.html
  • Build/src/main.ts
  • Build/src/runner.ts
  • Build/src/state.ts
  • Build/src/types.ts
  • Build/src/ui.ts
  • Build/styles/styles.css
  • Build/tsconfig.json
  • Build/vite.config.mjs
  • Build/webpack.config.js
  • README.md
  • TODO.md
  • main.js
  • manifest.json
  • service-worker.js
  • styles.css
💤 Files with no reviewable changes (10)
  • Build/webpack.config.js
  • service-worker.js
  • Build/Buildscripts/build-all.sh
  • manifest.json
  • Build/src/index.html
  • Build/Buildscripts/build.sh
  • styles.css
  • Build/src/state.ts
  • Build/Buildscripts/build-inline.sh
  • Build/tsconfig.json

Comment thread .github/workflows/single-file.yml
Comment thread Build/pwa-assets.config.js
Comment thread Build/src/appState.ts
Comment on lines +48 to +54
effect(() => {
if (!stateHydrated.get()) {
return;
}

localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()));
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Throttle persistence writes to localStorage.

This effect writes full serialized state on every content mutation; with editor updates, that can become a synchronous hot path and hurt typing responsiveness.

💡 Proposed fix
+let persistTimer: number | undefined;
+
 effect(() => {
   if (!stateHydrated.get()) {
     return;
   }
-
-  localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()));
+  if (persistTimer) window.clearTimeout(persistTimer);
+  persistTimer = window.setTimeout(() => {
+    localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()));
+  }, 150);
 });
📝 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
effect(() => {
if (!stateHydrated.get()) {
return;
}
localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()));
});
let persistTimer: number | undefined;
effect(() => {
if (!stateHydrated.get()) {
return;
}
if (persistTimer) window.clearTimeout(persistTimer);
persistTimer = window.setTimeout(() => {
localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()));
}, 150);
});
🤖 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 `@Build/src/appState.ts` around lines 48 - 54, The effect currently calls
localStorage.setItem("htmlRunnerState", JSON.stringify(createStateSnapshot()))
on every content mutation, causing sync hot-path writes; wrap this persistence
in a debounce/throttle so writes occur at a limited frequency (e.g., debounce
250–1000ms) or use requestIdleCallback to perform the write when idle, and
optionally skip writes if the new snapshot equals the last persisted snapshot.
Update the effect that references stateHydrated.get(), createStateSnapshot(),
and the localStorage.setItem call to schedule the serialized write via the
chosen debounce/throttle/idle mechanism and cancel any pending timer on cleanup.

Comment thread Build/src/appState.ts
Comment on lines +82 to +88
if (typeof snapshot.activeTab === "string") {
activeTabState.set(snapshot.activeTab);
}

if (typeof snapshot.activeOutput === "string") {
activeOutputState.set(snapshot.activeOutput);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate activeTab/activeOutput against allowed values before setting.

Current checks only validate type, so corrupted persisted data can store invalid keys and destabilize downstream UI/editor lookups.

💡 Proposed fix
-  if (typeof snapshot.activeTab === "string") {
+  if (snapshot.activeTab === "html" || snapshot.activeTab === "css" || snapshot.activeTab === "js") {
     activeTabState.set(snapshot.activeTab);
   }

-  if (typeof snapshot.activeOutput === "string") {
+  if (snapshot.activeOutput === "preview" || snapshot.activeOutput === "console") {
     activeOutputState.set(snapshot.activeOutput);
   }
📝 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
if (typeof snapshot.activeTab === "string") {
activeTabState.set(snapshot.activeTab);
}
if (typeof snapshot.activeOutput === "string") {
activeOutputState.set(snapshot.activeOutput);
}
if (snapshot.activeTab === "html" || snapshot.activeTab === "css" || snapshot.activeTab === "js") {
activeTabState.set(snapshot.activeTab);
}
if (snapshot.activeOutput === "preview" || snapshot.activeOutput === "console") {
activeOutputState.set(snapshot.activeOutput);
}
🤖 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 `@Build/src/appState.ts` around lines 82 - 88, The code currently only
type-checks snapshot.activeTab/activeOutput before calling
activeTabState.set/activeOutputState.set, which lets invalid persisted strings
propagate; update this to whitelist values: define or import the canonical
allowed sets (e.g., TAB_KEYS or an enum used by the UI/editor and OUTPUT_KEYS)
and verify snapshot.activeTab and snapshot.activeOutput are members (e.g.,
allowedTabs.includes(snapshot.activeTab)) before calling
activeTabState.set/activeOutputState.set; if a value is invalid, skip setting
(or set a safe default) and optionally log a warning so downstream lookups
remain stable.

<link rel="stylesheet" href="styles.css">
</head>
<body>
<script src="main.js"><\/script>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align default script filename with export output.

Line 8 points to main.js, but export currently writes script.js, so the default exported project loses JS execution out of the box.

💡 Proposed fix
-<script src="main.js"><\/script>
+<script src="script.js"><\/script>
📝 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
<script src="main.js"><\/script>
<script src="script.js"><\/script>
🤖 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 `@Build/src/defaultContent.ts` at line 8, The default HTML in
Build/src/defaultContent.ts references <script src="main.js"></script> but the
build/export emits "script.js", so update the script tag in the default export
to <script src="script.js"></script> (i.e., replace "main.js" with "script.js"
in the default content string) so the exported project loads the correct JS file
out of the box.

Comment thread Build/src/runner.ts
Comment on lines +17 to +33
if (!prettierBundlePromise) {
prettierBundlePromise = Promise.all([
import("prettier/standalone"),
import("prettier/plugins/html"),
import("prettier/plugins/postcss"),
import("prettier/plugins/babel"),
import("prettier/plugins/estree"),
]).then(
([prettier, parserHtml, parserCss, parserBabel, prettierPluginEstree]) => ({
prettier,
parserHtml,
parserCss,
parserBabel,
prettierPluginEstree,
})
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset cached Prettier promise on load failure.

If dynamic import fails once, the rejected promise is retained and all future format attempts fail until reload.

💡 Proposed fix
   if (!prettierBundlePromise) {
     prettierBundlePromise = Promise.all([
       import("prettier/standalone"),
       import("prettier/plugins/html"),
       import("prettier/plugins/postcss"),
       import("prettier/plugins/babel"),
       import("prettier/plugins/estree"),
     ]).then(
       ([prettier, parserHtml, parserCss, parserBabel, prettierPluginEstree]) => ({
         prettier,
         parserHtml,
         parserCss,
         parserBabel,
         prettierPluginEstree,
       })
-    );
+    ).catch((error) => {
+      prettierBundlePromise = undefined;
+      throw error;
+    });
   }
📝 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
if (!prettierBundlePromise) {
prettierBundlePromise = Promise.all([
import("prettier/standalone"),
import("prettier/plugins/html"),
import("prettier/plugins/postcss"),
import("prettier/plugins/babel"),
import("prettier/plugins/estree"),
]).then(
([prettier, parserHtml, parserCss, parserBabel, prettierPluginEstree]) => ({
prettier,
parserHtml,
parserCss,
parserBabel,
prettierPluginEstree,
})
);
}
if (!prettierBundlePromise) {
prettierBundlePromise = Promise.all([
import("prettier/standalone"),
import("prettier/plugins/html"),
import("prettier/plugins/postcss"),
import("prettier/plugins/babel"),
import("prettier/plugins/estree"),
]).then(
([prettier, parserHtml, parserCss, parserBabel, prettierPluginEstree]) => ({
prettier,
parserHtml,
parserCss,
parserBabel,
prettierPluginEstree,
})
).catch((error) => {
prettierBundlePromise = undefined;
throw error;
});
}
🤖 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 `@Build/src/runner.ts` around lines 17 - 33, The cached promise
prettierBundlePromise currently holds a rejected promise if the dynamic imports
fail, causing all future format attempts to fail; modify the initialization in
the import block so the Promise.all(...).then(...) chain also has a .catch(err
=> { prettierBundlePromise = undefined; throw err; }) (or similar reset) so that
on load failure the cached prettierBundlePromise is cleared and subsequent calls
retry the dynamic imports; reference the prettierBundlePromise variable and the
Promise.all([... import(...) ...]).then(...) chain in runner.ts when applying
this change.

Comment thread Build/vite.config.mjs
Comment on lines +7 to +25
function inlineSvgFaviconPlugin(options) {
return {
name: "inline-svg-favicon",
enforce: "post",
transformIndexHtml(html) {
if (!fs.existsSync(options.svg)) return html;
let svgContent = fs.readFileSync(options.svg, "utf8");
// Remove XML header if present, minify spaces
svgContent = svgContent
.replace(/<\?xml[^>]*>\s*/g, "")
.replace(/\s+/g, " ");
// Base64 encode the SVG
const base64 = Buffer.from(svgContent).toString("base64");
const faviconTag = `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${base64}"/>\n`;
// Insert favicon into <head>
return html.replace(/<head>(.*?)/, `<head>$1\n ${faviconTag}`);
},
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: Plugin designed for SVG but receives PNG file.

The inlineSvgFaviconPlugin expects an SVG file but is given "public/favicon.png" at line 69. This causes multiple issues:

  1. Line 13: Reads PNG binary data as UTF-8 text, corrupting the data
  2. Lines 15-17: Attempts to remove XML headers and minify whitespace from PNG binary
  3. Line 19: Base64-encodes corrupted data
  4. Result: Broken favicon in single-file builds

Solutions:

Option 1 (Recommended): Convert favicon to SVG format

  • Save public/favicon.png as public/favicon.svg
  • Update line 69 to reference the SVG file

Option 2: Fix the plugin to handle PNG correctly

🐛 Proposed fix for Option 2 (handle PNG correctly)
-function inlineSvgFaviconPlugin(options) {
+function inlineFaviconPlugin(options) {
   return {
-    name: "inline-svg-favicon",
+    name: "inline-favicon",
     enforce: "post",
     transformIndexHtml(html) {
-      if (!fs.existsSync(options.svg)) return html;
-      let svgContent = fs.readFileSync(options.svg, "utf8");
-      // Remove XML header if present, minify spaces
-      svgContent = svgContent
-        .replace(/<\?xml[^>]*>\s*/g, "")
-        .replace(/\s+/g, " ");
-      // Base64 encode the SVG
-      const base64 = Buffer.from(svgContent).toString("base64");
-      const faviconTag = `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${base64}"/>\n`;
+      if (!fs.existsSync(options.image)) return html;
+      const imageBuffer = fs.readFileSync(options.image);
+      const ext = options.image.split('.').pop().toLowerCase();
+      const mimeType = ext === 'svg' ? 'image/svg+xml' : `image/${ext}`;
+      const base64 = imageBuffer.toString("base64");
+      const faviconTag = `<link rel="icon" type="${mimeType}" href="data:${mimeType};base64,${base64}"/>\n`;
       // Insert favicon into <head>
       return html.replace(/<head>(.*?)/, `<head>$1\n  ${faviconTag}`);
     },
   };
 }

And update the call site:

-      isSingleFile && inlineSvgFaviconPlugin({ svg: "public/favicon.png" }),
+      isSingleFile && inlineFaviconPlugin({ image: "public/favicon.png" }),
📝 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
function inlineSvgFaviconPlugin(options) {
return {
name: "inline-svg-favicon",
enforce: "post",
transformIndexHtml(html) {
if (!fs.existsSync(options.svg)) return html;
let svgContent = fs.readFileSync(options.svg, "utf8");
// Remove XML header if present, minify spaces
svgContent = svgContent
.replace(/<\?xml[^>]*>\s*/g, "")
.replace(/\s+/g, " ");
// Base64 encode the SVG
const base64 = Buffer.from(svgContent).toString("base64");
const faviconTag = `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${base64}"/>\n`;
// Insert favicon into <head>
return html.replace(/<head>(.*?)/, `<head>$1\n ${faviconTag}`);
},
};
}
function inlineFaviconPlugin(options) {
return {
name: "inline-favicon",
enforce: "post",
transformIndexHtml(html) {
if (!fs.existsSync(options.image)) return html;
const imageBuffer = fs.readFileSync(options.image);
const ext = options.image.split('.').pop().toLowerCase();
const mimeType = ext === 'svg' ? 'image/svg+xml' : `image/${ext}`;
const base64 = imageBuffer.toString("base64");
const faviconTag = `<link rel="icon" type="${mimeType}" href="data:${mimeType};base64,${base64}"/>\n`;
// Insert favicon into <head>
return html.replace(/<head>(.*?)/, `<head>$1\n ${faviconTag}`);
},
};
}
🤖 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 `@Build/vite.config.mjs` around lines 7 - 25, The plugin inlineSvgFaviconPlugin
is treating the file at options.svg as UTF-8 SVG (transformIndexHtml reads it as
text and runs XML/whitespace regex) but the project supplies a PNG, corrupting
the favicon; fix by either (A) replacing the PNG with a real SVG and updating
the favicon reference to public/favicon.svg so inlineSvgFaviconPlugin can
continue to read UTF-8 SVG, or (B) update
inlineSvgFaviconPlugin/transformIndexHtml to detect the file type from
options.svg (e.g., by extension or mime sniff), read binary for non-SVG files
(fs.readFileSync(options.svg) without "utf8"), skip the XML/whitespace regex for
binary images, base64-encode the raw buffer, and set the correct type in
faviconTag (image/png vs image/svg+xml) before inserting into the head.

Comment thread TODO.md
- [ ] **Version Control** - Built-in Git integration using [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)
- [ ] **Template Library** - Pre-built code snippets
- [ ] **Performance Profiler** - Analyze code performance
- [ ] **Auto Completion** (using the built in CodeMirror tools)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hyphenate “built-in” in the Auto Completion item.

Line 12 has a small grammar issue: “built in” should be “built-in” as a compound adjective.

✏️ Suggested edit
-- [ ] **Auto Completion** (using the built in CodeMirror tools)
+- [ ] **Auto Completion** (using the built-in CodeMirror tools)
📝 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
- [ ] **Auto Completion** (using the built in CodeMirror tools)
- [ ] **Auto Completion** (using the built-in CodeMirror tools)
🧰 Tools
🪛 LanguageTool

[grammar] ~12-~12: Use a hyphen to join words.
Context: ...[ ] Auto Completion (using the built in CodeMirror tools) - [ ] **Code Foldin...

(QB_NEW_EN_HYPHEN)

🤖 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 `@TODO.md` at line 12, Update the Auto Completion checklist item text to
hyphenate “built-in” in the phrase inside the markdown list item (the line
containing "- [ ] **Auto Completion** (using the built in CodeMirror tools)");
change "built in" to "built-in" so the item reads "(using the built-in
CodeMirror tools)".

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