diff --git a/docs/screenshots/01-desktop.png b/docs/screenshots/01-desktop.png
new file mode 100644
index 0000000..0fdaeff
Binary files /dev/null and b/docs/screenshots/01-desktop.png differ
diff --git a/docs/screenshots/02-start-menu.png b/docs/screenshots/02-start-menu.png
new file mode 100644
index 0000000..6d42f46
Binary files /dev/null and b/docs/screenshots/02-start-menu.png differ
diff --git a/docs/screenshots/03-terminal.png b/docs/screenshots/03-terminal.png
new file mode 100644
index 0000000..e3ce0ed
Binary files /dev/null and b/docs/screenshots/03-terminal.png differ
diff --git a/docs/screenshots/04-file-explorer.png b/docs/screenshots/04-file-explorer.png
new file mode 100644
index 0000000..84e8416
Binary files /dev/null and b/docs/screenshots/04-file-explorer.png differ
diff --git a/docs/screenshots/05-browser.png b/docs/screenshots/05-browser.png
new file mode 100644
index 0000000..626f5e7
Binary files /dev/null and b/docs/screenshots/05-browser.png differ
diff --git a/docs/screenshots/06-code-editor.png b/docs/screenshots/06-code-editor.png
new file mode 100644
index 0000000..d2201cc
Binary files /dev/null and b/docs/screenshots/06-code-editor.png differ
diff --git a/docs/screenshots/07-system-monitor.png b/docs/screenshots/07-system-monitor.png
new file mode 100644
index 0000000..e2d79ef
Binary files /dev/null and b/docs/screenshots/07-system-monitor.png differ
diff --git a/docs/screenshots/08-terminal-commands.png b/docs/screenshots/08-terminal-commands.png
new file mode 100644
index 0000000..4e8a655
Binary files /dev/null and b/docs/screenshots/08-terminal-commands.png differ
diff --git a/docs/screenshots/README.md b/docs/screenshots/README.md
new file mode 100644
index 0000000..e70c1dd
--- /dev/null
+++ b/docs/screenshots/README.md
@@ -0,0 +1,33 @@
+# Playwright Test Report – Hacker Simulator (JS app)
+
+The web application (`/src`) was built and served locally, then driven end‑to‑end
+with Playwright (headless Chrome). Every desktop application, the Start menu and
+the terminal command processor were exercised. The bugs found during testing were
+fixed; after the fixes the app loads and runs with **no application console/page
+errors** (the only remaining console messages are external CDN resources –
+Google Fonts and the xterm stylesheet – which fail only in a fully offline
+sandbox and degrade gracefully).
+
+## Issues found and fixed
+
+| # | Area | Problem | Fix |
+|---|------|---------|-----|
+| 1 | Build | `src/styles/main.less` imported Google Fonts via `@import url(...)`, which `less-loader` resolves at **build time** and fails offline, breaking the whole build. | Load the font at runtime with a `` in `index.html`; removed the build‑time `@import`. |
+| 2 | Startup | Start Menu read the IndexedDB‑backed filesystem in its constructor (before `fileSystem.init()`), throwing `Database not initialized` and an unhandled page error. | Defer `buildPinedApps()` to the existing `os.Ready()` callback, after the filesystem is initialized. |
+| 3 | Code Editor | Monaco loaded its web worker from `https://unpkg.com/monaco-editor@latest/...`, which fails offline and risks a version mismatch with the bundled Monaco. | Use webpack‑bundled workers via `new Worker(new URL('monaco-editor/esm/...', import.meta.url))`. |
+| 4 | Theme init | `ThemeManager` used `stat()` (which logs an error for a missing path) to test whether `/etc/themes` exists, logging an error on every startup. | Use `exists()` instead, treating a missing directory as an expected condition. |
+| 5 | Page metadata | The app shipped no favicon, so every load produced a `GET /favicon.ico 404`. | Added an inline SVG favicon (no external file, works offline). |
+| 6 | Blazor build | Stray extra `}` in `WindowStateTest.razor.cs` broke the `wasm2` Blazor build. | Removed the extra brace. |
+
+## Screenshots
+
+| View | File |
+|------|------|
+| Desktop | `01-desktop.png` |
+| Start menu (pinned app tiles with colors) | `02-start-menu.png` |
+| Terminal | `03-terminal.png` |
+| File Explorer | `04-file-explorer.png` |
+| Browser (in‑game HackerSearch) | `05-browser.png` |
+| Code Editor (Monaco, locally bundled) | `06-code-editor.png` |
+| System Monitor | `07-system-monitor.png` |
+| Terminal running `help` and `ls /` | `08-terminal-commands.png` |
diff --git a/src/apps/code-editor.ts b/src/apps/code-editor.ts
index 4cb07ab..c36232a 100644
--- a/src/apps/code-editor.ts
+++ b/src/apps/code-editor.ts
@@ -155,19 +155,30 @@ export class CodeEditorApp extends GuiApplication {
private initMonaco(): void {
if (!this.editorContainer) return;
- // Configure Monaco workers
+ // Configure Monaco workers to use the locally bundled worker files.
+ // webpack 5 bundles each `new Worker(new URL(...))` into its own chunk,
+ // so the editor works offline and always matches the bundled Monaco
+ // version (previously these were fetched from unpkg.com@latest, which
+ // failed offline and risked version mismatches).
(window as any).MonacoEnvironment = {
getWorker: function(_moduleId: string, label: string) {
- // Use a proxied worker that doesn't rely on specific worker files
- // This is a workaround when worker files aren't properly bundled
- const workerContent = `
- self.MonacoEnvironment = {
- baseUrl: 'https://unpkg.com/monaco-editor@latest/min/'
- };
- importScripts('https://unpkg.com/monaco-editor@latest/min/vs/base/worker/workerMain.js');
- `;
- const blob = new Blob([workerContent], { type: 'application/javascript' });
- return new Worker(URL.createObjectURL(blob));
+ switch (label) {
+ case 'json':
+ return new Worker(new URL('monaco-editor/esm/vs/language/json/json.worker.js', import.meta.url));
+ case 'css':
+ case 'scss':
+ case 'less':
+ return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker.js', import.meta.url));
+ case 'html':
+ case 'handlebars':
+ case 'razor':
+ return new Worker(new URL('monaco-editor/esm/vs/language/html/html.worker.js', import.meta.url));
+ case 'typescript':
+ case 'javascript':
+ return new Worker(new URL('monaco-editor/esm/vs/language/typescript/ts.worker.js', import.meta.url));
+ default:
+ return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url));
+ }
}
};
diff --git a/src/core/ThemeManager.ts b/src/core/ThemeManager.ts
index 766cae7..0b2730c 100644
--- a/src/core/ThemeManager.ts
+++ b/src/core/ThemeManager.ts
@@ -416,13 +416,12 @@ export class ThemeManager {
* Make sure the themes directory exists
*/
private async ensureThemesDirectory(): Promise {
- try {
- // Check if directory exists
- await this.fileSystem.stat(this.themesDirectory);
- } catch (error) {
- // Directory doesn't exist, create it
+ // Use exists() rather than stat() so a missing directory is treated as an
+ // expected condition instead of logging an error. The directory is created
+ // on first run when it does not yet exist.
+ const exists = await this.fileSystem.exists(this.themesDirectory);
+ if (!exists) {
await this.fileSystem.createDirectory(this.themesDirectory, null, true);
-
}
}
diff --git a/src/core/start-menu.ts b/src/core/start-menu.ts
index 5faa79e..6456789 100644
--- a/src/core/start-menu.ts
+++ b/src/core/start-menu.ts
@@ -93,9 +93,16 @@ export class StartMenuController {
- this.os.Ready(() => {
- // Load pinned apps from user settings
- this.loadPinnedApps();
+ this.os.Ready(async () => {
+ // Load pinned apps from user settings. This reads from the virtual
+ // filesystem (IndexedDB), so it must only run once the OS – and in
+ // particular the filesystem – has finished initializing.
+ await this.loadPinnedApps();
+
+ // Build the pinned apps view now that the filesystem is ready. This is
+ // intentionally not done in the constructor to avoid accessing the
+ // database before it is initialized.
+ await this.buildPinedApps();
// Initialize Lucide icons
this.initIcons();
@@ -142,10 +149,11 @@ export class StartMenuController {
}
}
- // If we have pinned apps, use them, otherwise keep the default
+ // If we have pinned apps, use them, otherwise keep the default.
+ // The actual rendering (buildPinedApps) is performed by the caller
+ // once loading is complete, so we only update the config here.
if (newTileConfig.length > 0) {
this.appTileConfig = newTileConfig;
- this.buildPinedApps();
}
}
}
@@ -215,7 +223,9 @@ export class StartMenuController {
const pinnedApps = document.createElement('div');
pinnedApps.className = 'pinned-apps'; // Add app tiles
this.pinnedAppsEl = pinnedApps;
- this.buildPinedApps();
+ // NOTE: Pinned apps are built later, once the OS/filesystem is ready
+ // (see the os.Ready callback in the constructor). Building them here would
+ // access the IndexedDB-backed filesystem before it is initialized.
pinnedAppsView.appendChild(pinnedApps);
content.appendChild(pinnedAppsView);
diff --git a/src/index.html b/src/index.html
index 52b1504..9af85e3 100644
--- a/src/index.html
+++ b/src/index.html
@@ -3,6 +3,10 @@
Hacker Simulator
+
+
+
+
diff --git a/src/styles/main.less b/src/styles/main.less
index 72c44c4..f8b8744 100644
--- a/src/styles/main.less
+++ b/src/styles/main.less
@@ -1,5 +1,7 @@
// Main LESS file that imports all other style components
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
+// Note: The "Inter" web font is loaded at runtime via a tag in index.html.
+// It is intentionally not imported here so the build does not depend on fetching
+// a remote resource (which fails in offline/sandboxed environments).
@import "variables.less";
@import "base.less";
@import "taskbar.less";
diff --git a/wasm2/HackerOs/HackerOs/OS/Applications/UI/Windows/Test/WindowStateTest.razor.cs b/wasm2/HackerOs/HackerOs/OS/Applications/UI/Windows/Test/WindowStateTest.razor.cs
index dbc31a8..cbf6065 100644
--- a/wasm2/HackerOs/HackerOs/OS/Applications/UI/Windows/Test/WindowStateTest.razor.cs
+++ b/wasm2/HackerOs/HackerOs/OS/Applications/UI/Windows/Test/WindowStateTest.razor.cs
@@ -77,4 +77,3 @@ protected override void OnAfterRender(bool firstRender)
}
}
}
-}