web-a2e 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,375 @@
1
+ /*
2
+ * window-manager.js - Window manager for all windows
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ export class WindowManager {
9
+ constructor() {
10
+ this.windows = new Map();
11
+ this.highestZIndex = 1000;
12
+ this.storageKey = 'a2e-debug-windows';
13
+
14
+ // Bind and set up window resize listener to keep windows in viewport
15
+ this.handleWindowResize = this.handleWindowResize.bind(this);
16
+ window.addEventListener('resize', this.handleWindowResize);
17
+ }
18
+
19
+ /**
20
+ * Handle browser window resize - constrain all windows to viewport
21
+ */
22
+ handleWindowResize() {
23
+ this.constrainAllToViewport();
24
+ }
25
+
26
+ /**
27
+ * Register a window with the manager
28
+ */
29
+ register(window) {
30
+ this.windows.set(window.id, window);
31
+
32
+ // Set up callbacks
33
+ window.onFocus = (id) => this.bringToFront(id);
34
+ window.onStateChange = () => this.saveState();
35
+ }
36
+
37
+ /**
38
+ * Get a window by ID
39
+ */
40
+ getWindow(id) {
41
+ return this.windows.get(id);
42
+ }
43
+
44
+ /**
45
+ * Show a specific window
46
+ */
47
+ showWindow(id) {
48
+ const window = this.windows.get(id);
49
+ if (window) {
50
+ window.show();
51
+ this.bringToFront(id);
52
+ this.saveState();
53
+ if (typeof umami !== 'undefined') {
54
+ umami.track(`window-open-${id}`);
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Hide a specific window
61
+ */
62
+ hideWindow(id) {
63
+ const window = this.windows.get(id);
64
+ if (window) {
65
+ window.hide();
66
+ window.element.classList.remove('focused');
67
+ this.focusTopWindow();
68
+ this.saveState();
69
+ if (typeof umami !== 'undefined') {
70
+ umami.track(`window-close-${id}`);
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Toggle a window's visibility
77
+ */
78
+ toggleWindow(id) {
79
+ const window = this.windows.get(id);
80
+ if (window) {
81
+ window.toggle();
82
+ if (window.isVisible) {
83
+ this.bringToFront(id);
84
+ if (typeof umami !== 'undefined') {
85
+ umami.track(`window-open-${id}`);
86
+ }
87
+ } else {
88
+ window.element.classList.remove('focused');
89
+ this.focusTopWindow();
90
+ if (typeof umami !== 'undefined') {
91
+ umami.track(`window-close-${id}`);
92
+ }
93
+ }
94
+ this.saveState();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Check if a window is visible
100
+ */
101
+ isWindowVisible(id) {
102
+ const window = this.windows.get(id);
103
+ return window ? window.isVisible : false;
104
+ }
105
+
106
+ /**
107
+ * Hide all windows
108
+ */
109
+ hideAll() {
110
+ for (const window of this.windows.values()) {
111
+ window.hide();
112
+ window.element.classList.remove('focused');
113
+ }
114
+ this.saveState();
115
+ }
116
+
117
+ /**
118
+ * Bring a window to the front.
119
+ * Caps z-index below 2000 so header dropdown menus always render on top.
120
+ */
121
+ bringToFront(id) {
122
+ this.highestZIndex++;
123
+ if (this.highestZIndex >= 1900) {
124
+ this.normalizeZIndices(id);
125
+ } else {
126
+ const window = this.windows.get(id);
127
+ if (window) {
128
+ window.setZIndex(this.highestZIndex);
129
+ }
130
+ }
131
+ this.setFocused(id);
132
+ }
133
+
134
+ /**
135
+ * Mark a window as focused, removing focus from all others
136
+ */
137
+ setFocused(id) {
138
+ for (const [winId, win] of this.windows) {
139
+ if (winId === id) {
140
+ win.element.classList.add('focused');
141
+ } else {
142
+ win.element.classList.remove('focused');
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Focus the topmost visible window by z-index
149
+ */
150
+ focusTopWindow() {
151
+ let topWin = null;
152
+ let topZ = -1;
153
+ for (const win of this.windows.values()) {
154
+ if (win.isVisible && (win.zIndex || 0) > topZ) {
155
+ topZ = win.zIndex || 0;
156
+ topWin = win;
157
+ }
158
+ }
159
+ if (topWin) {
160
+ topWin.element.classList.add('focused');
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Reassign z-indices starting from 1000, preserving the current stacking order.
166
+ * The window identified by frontId is placed on top.
167
+ */
168
+ normalizeZIndices(frontId) {
169
+ // Collect windows that have a z-index (visible or not) and sort by current z
170
+ const ordered = [...this.windows.entries()]
171
+ .filter(([id]) => id !== frontId)
172
+ .sort((a, b) => (a[1].zIndex || 0) - (b[1].zIndex || 0));
173
+
174
+ let z = 1000;
175
+ for (const [, win] of ordered) {
176
+ win.setZIndex(z++);
177
+ }
178
+ const front = this.windows.get(frontId);
179
+ if (front) {
180
+ front.setZIndex(z);
181
+ }
182
+ this.highestZIndex = z;
183
+ }
184
+
185
+ /**
186
+ * Save all window states to localStorage
187
+ */
188
+ saveState() {
189
+ try {
190
+ const state = {};
191
+ for (const [id, window] of this.windows) {
192
+ state[id] = window.getState();
193
+ }
194
+ localStorage.setItem(this.storageKey, JSON.stringify(state));
195
+ } catch (e) {
196
+ console.warn('Could not save debug window state:', e);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Load window states from localStorage
202
+ */
203
+ loadState() {
204
+ try {
205
+ const saved = localStorage.getItem(this.storageKey);
206
+ if (saved) {
207
+ const state = JSON.parse(saved);
208
+ // First pass: restore positions, sizes, and visibility
209
+ // (show() calls bringToFront() which assigns temporary z-indices)
210
+ for (const [id, windowState] of Object.entries(state)) {
211
+ const window = this.windows.get(id);
212
+ if (window) {
213
+ window.restoreState(windowState);
214
+ }
215
+ }
216
+ // Second pass: restore saved z-indices, overriding those assigned by show()
217
+ for (const [id, windowState] of Object.entries(state)) {
218
+ const window = this.windows.get(id);
219
+ if (window && windowState.zIndex !== undefined) {
220
+ window.setZIndex(windowState.zIndex);
221
+ if (windowState.zIndex > this.highestZIndex) {
222
+ this.highestZIndex = windowState.zIndex;
223
+ }
224
+ }
225
+ }
226
+ this.focusTopWindow();
227
+ }
228
+ } catch (e) {
229
+ console.warn('Could not load debug window state:', e);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Clear all saved window state (useful for debugging)
235
+ */
236
+ clearState() {
237
+ try {
238
+ localStorage.removeItem(this.storageKey);
239
+ console.log('Debug window state cleared');
240
+ } catch (e) {
241
+ console.warn('Could not clear debug window state:', e);
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Update all visible windows
247
+ */
248
+ updateAll(wasmModule) {
249
+ for (const window of this.windows.values()) {
250
+ if (window.isVisible) {
251
+ window.update(wasmModule);
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Get IDs of all visible windows
258
+ */
259
+ getVisibleWindowIds() {
260
+ const ids = [];
261
+ for (const [id, window] of this.windows) {
262
+ if (window.isVisible) {
263
+ ids.push(id);
264
+ }
265
+ }
266
+ return ids;
267
+ }
268
+
269
+ /**
270
+ * Cycle focus to the next visible window in z-index order.
271
+ * If reverse is true, cycle to the previous window instead.
272
+ */
273
+ cycleWindow(reverse = false) {
274
+ const visible = [...this.windows.values()]
275
+ .filter(w => w.isVisible)
276
+ .sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
277
+ if (visible.length === 0) return;
278
+
279
+ // The focused window is the one with the highest z-index (last in sorted order)
280
+ const focusedIndex = visible.length - 1;
281
+
282
+ // Cycling forward brings the bottom window to the top;
283
+ // cycling in reverse brings the second-from-top to the top
284
+ let nextIndex;
285
+ if (reverse) {
286
+ nextIndex = focusedIndex - 1;
287
+ if (nextIndex < 0) nextIndex = visible.length - 1;
288
+ } else {
289
+ nextIndex = 0;
290
+ }
291
+
292
+ this.bringToFront(visible[nextIndex].id);
293
+ }
294
+
295
+ /**
296
+ * Constrain all windows to the visible viewport
297
+ * Call this on window resize to prevent windows from being off-screen
298
+ */
299
+ constrainAllToViewport() {
300
+ for (const window of this.windows.values()) {
301
+ window.constrainToViewport();
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Apply default layout for first-time users (no saved state).
307
+ * Each entry: { id, x, y, width, height, visible, position, viewportLocked }
308
+ * Use position: "viewport-fill" for a window that should fill the viewport.
309
+ */
310
+ applyDefaultLayout(layout) {
311
+ const savedState = localStorage.getItem(this.storageKey);
312
+ if (savedState) {
313
+ try {
314
+ const parsed = JSON.parse(savedState);
315
+ if (parsed && Object.keys(parsed).length > 0) return;
316
+ } catch (e) { /* proceed with defaults */ }
317
+ }
318
+
319
+ for (const entry of layout) {
320
+ const win = this.windows.get(entry.id);
321
+ if (!win) continue;
322
+
323
+ if (entry.position === 'viewport-fill') {
324
+ const header = document.querySelector('header');
325
+ const footer = document.querySelector('footer');
326
+ const headerH = header ? header.offsetHeight : 0;
327
+ const footerH = footer ? footer.offsetHeight : 0;
328
+ const margin = 8;
329
+
330
+ const w = window.innerWidth - margin * 2;
331
+ const h = window.innerHeight - headerH - footerH - margin * 2;
332
+ const x = margin;
333
+ const y = headerH + margin;
334
+
335
+ win.element.style.left = `${x}px`;
336
+ win.element.style.top = `${y}px`;
337
+ win.element.style.width = `${w}px`;
338
+ win.element.style.height = `${h}px`;
339
+ win.currentX = x;
340
+ win.currentY = y;
341
+ win.currentWidth = w;
342
+ win.currentHeight = h;
343
+ } else {
344
+ if (entry.x !== undefined) {
345
+ win.element.style.left = `${entry.x}px`;
346
+ win.currentX = entry.x;
347
+ }
348
+ if (entry.y !== undefined) {
349
+ win.element.style.top = `${entry.y}px`;
350
+ win.currentY = entry.y;
351
+ }
352
+ if (entry.width !== undefined) {
353
+ const w = Math.min(win.maxWidth, Math.max(entry.width, win.minWidth));
354
+ win.element.style.width = `${w}px`;
355
+ win.currentWidth = w;
356
+ }
357
+ if (entry.height !== undefined) {
358
+ const h = Math.min(win.maxHeight, Math.max(entry.height, win.minHeight));
359
+ win.element.style.height = `${h}px`;
360
+ win.currentHeight = h;
361
+ }
362
+ }
363
+
364
+ if (entry.viewportLocked && typeof win.setViewportLocked === 'function') {
365
+ win.setViewportLocked(true);
366
+ }
367
+
368
+ if (entry.visible) {
369
+ win.show();
370
+ this.bringToFront(entry.id);
371
+ }
372
+ }
373
+ }
374
+
375
+ }