Refactor Pixel Forge into modular app architecture with persistence, PWA baseline, accessibility, and export enhancements - #1
Conversation
Co-authored-by: TalaeiDev <90834494+TalaeiDev@users.noreply.github.com>
Co-authored-by: TalaeiDev <90834494+TalaeiDev@users.noreply.github.com>
TalaeiDev
left a comment
There was a problem hiding this comment.
update page performance and structure
There was a problem hiding this comment.
🟡 Changes recommended
There are a few correctness/robustness and accessibility gaps in the new offline caching, persistence error-handling, and exported SVG/PNG output that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR refactors Pixel Forge from a single large HTML file into a modular static app (separate CSS/JS), and adds baseline production features including persistence, export improvements, and PWA/offline support.
Changes:
- Split UI styling into
styles/*and core behavior intojs/*, withindex.htmlas the main entrypoint andpixel-forge-page.htmlas a redirect shim. - Added persistence via localStorage autosave/manual save + status indicators, plus PNG/SVG export helpers.
- Added PWA baseline:
manifest.json,service-worker.js, install prompt wiring, and an app icon.
File summaries
| File | Description |
|---|---|
| styles/theme.css | Adds theme variables, focus-visible outlines, and sr-only helper. |
| styles/layout.css | Defines the app grid layout, panels, and workspace base styling. |
| styles/components.css | Adds/organizes component styles (rooms/doors/hallways, tooltips, modal, breakpoints). |
| service-worker.js | Introduces offline cache + fetch caching behavior for PWA baseline. |
| README.md | Updates docs to reflect modular structure, features, and development notes. |
| pixel-forge-page.html | Converts prior monolith page into an index.html redirect shim. |
| manifest.json | Adds basic PWA manifest metadata and icon. |
| js/utils.js | Adds shared debounce/raf-throttle/download/time helpers. |
| js/tools.js | Adds install prompt binding and service worker registration helpers. |
| js/storage.js | Adds localStorage JSON helpers and shared keys. |
| js/state.js | Adds a lightweight metrics store for runtime counters. |
| js/canvas.js | Adds SVG/PNG export helpers. |
| js/app.js | Main editor logic, persistence/autosave, accessibility defaults, and performance scheduling. |
| index.html | New primary app shell with PWA meta, controls, modal, and script/style includes. |
| icons/icon.svg | Adds an app icon for the manifest/favicon. |
Review details
Suppressed comments (2)
index.html:85
- The mini-map canvas has no accessible name; the JS fallback will end up using the element id ("miniMapCanvas"), which is not user-friendly for assistive tech.
<div class="mini-map" id="miniMap">
<div class="mini-label">🗺️ Overview</div>
<canvas id="miniMapCanvas"></canvas>
<div class="mini-viewport" id="miniViewport"></div>
index.html:204
- The shortcuts modal is missing dialog semantics (role/aria-modal) and an aria-labelledby hook, which makes it harder for screen readers to announce the modal context and title correctly.
<div id="shortcutsModal" class="modal" aria-hidden="true">
<div class="modal-content">
<h2>Keyboard shortcuts</h2>
<p>1 Draw · 2 Select · 3 Door · 4 Hallway</p>
<p>Ctrl+Z Undo · Ctrl+Y Redo · Ctrl+D Duplicate · Del Delete</p>
- Files reviewed: 13/15 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <button id="deleteButton" class="danger">✕</button> | ||
| <button id="clearButton" class="danger">🗑</button> |
| buildSvg(node) { | ||
| const width = Math.max(1, node.scrollWidth); | ||
| const height = Math.max(1, node.scrollHeight); | ||
| const clone = node.cloneNode(true); | ||
| clone.style.transform = 'none'; | ||
| clone.querySelectorAll('.grid-layer,.room-tooltip,.context-menu,.mini-map').forEach((el) => el.remove()); | ||
| const serializer = new XMLSerializer(); | ||
| const body = serializer.serializeToString(clone); | ||
| return `<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><foreignObject x="0" y="0" width="100%" height="100%">${body}</foreignObject></svg>`; | ||
| } |
| saveJSON(key, value) { | ||
| localStorage.setItem(key, JSON.stringify(value)); | ||
| } |
| self.addEventListener('fetch', (event) => { | ||
| if (event.request.method !== 'GET') return; | ||
| event.respondWith( | ||
| caches.match(event.request).then((cached) => cached || fetch(event.request).then((response) => { | ||
| const responseClone = response.clone(); | ||
| caches.open(CACHE_NAME).then((cache) => cache.put(event.request, responseClone)); | ||
| return response; | ||
| }).catch(() => caches.match('./index.html').then((response) => response || Response.error()))) | ||
| ); | ||
| }); |
The dungeon designer was constrained by a single 2.6k+ line HTML file, making iteration and reliability difficult. This change restructures the app into modular assets and adds missing production features: persistence, offline/PWA support, accessibility upgrades, responsive breakpoints, and broader export capabilities.
Architecture split (monolith → modules)
index.htmlas the primary entrypoint and split styles/scripts into:styles/theme.css,styles/layout.css,styles/components.cssjs/app.js,js/canvas.js,js/tools.js,js/state.js,js/utils.js,js/storage.jspixel-forge-page.htmlinto a redirect shim toindex.htmlfor compatibility.Persistence and project lifecycle
projectName, timestamps).Export/import capabilities
js/canvas.js.PWA and offline baseline
manifest.json,service-worker.js, and app icon.index.html.Accessibility and UX
Performance and responsiveness
requestAnimationFramethrottling for mini-map redraw scheduling.768px,480px, and320px(in addition to existing compact behavior).