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) } } } -}