169 lines
11 KiB
Markdown
169 lines
11 KiB
Markdown
# Implementation Plan
|
||
|
||
[Overview]
|
||
Add a Doom emulator that launches in a modal window (“floating over the page”) when the user clicks a new “running on a potato” entry in the existing sidebar menu. The emulator will be loaded on-demand, client-only, and isolated from SSR to protect page performance and stability.
|
||
|
||
Multiple paragraphs outlining the scope, context, and high-level approach. Explain why this implementation is needed and how it fits into the existing system.
|
||
- This Next.js 15 app uses the App Router, React 19, Tailwind, and several client-side providers (Lenis, Motion). The request is to add a lightweight, non-intrusive Doom experience without altering the primary site navigation. The user wants a single menu item in the sidebar labeled “running on a potato” that, when clicked, opens an overlay modal containing the emulator.
|
||
- The overlay will be implemented as a client-only modal and mounted at the root layout so it renders above all content, and remains accessible across pages. We will add a React Context Provider to manage the “open/close” state of the modal globally. The sidebar menu will receive an action hook to open the modal.
|
||
- For the emulator engine, we will use js-dos (DOSBox compiled to WebAssembly) via the npm package “js-dos”. It can boot a packaged .jsdos archive containing DOS Doom and run entirely in the browser. This approach avoids adding complex custom build steps and does not require special COOP/COEP headers. The emulator will be lazy-loaded via dynamic import to keep initial page loads fast.
|
||
- To provide an immediate out-of-the-box experience, the emulator will load a remotely hosted demo .jsdos archive from the official js-dos CDN (subject to its availability). If remote loading fails (network/CORS), the overlay will show a prompt allowing the user to drag-and-drop or select a local .jsdos archive to run. This keeps the feature functional while avoiding bundling large binary assets into the repository.
|
||
- Accessibility and usability: the modal will trap focus, provide keyboard controls (ESC to close), restore focus on close, and disable background scroll while open.
|
||
|
||
[Types]
|
||
Single sentence describing the type system changes.
|
||
We will add a small set of TypeScript types for the modal provider context and emulator configuration.
|
||
|
||
Detailed type definitions, interfaces, enums, or data structures with complete specifications. Include field names, types, validation rules, and relationships.
|
||
- app/providers/DoomOverlayProvider.tsx
|
||
- export type DoomOverlayContextValue = {
|
||
isOpen: boolean;
|
||
open: () => void;
|
||
close: () => void;
|
||
}
|
||
- Constraints:
|
||
- isOpen: true when modal is visible.
|
||
- open/close: stable callbacks; must be usable from any client component.
|
||
- app/components/doom/JsDosPlayer.tsx
|
||
- export type JsDosPlayerProps = {
|
||
zipUrl?: string; // optional remote .jsdos archive URL
|
||
className?: string;
|
||
}
|
||
- export type JsDosHandle = {
|
||
stop: () => void; // shutdown emulator and free resources
|
||
}
|
||
- Emulator engine choice type:
|
||
- export enum DoomEngine { JsDos = "js-dos" }
|
||
- export type DoomConfig = { engine: DoomEngine; jsdos?: { zipUrl?: string } }
|
||
|
||
[Files]
|
||
Single sentence describing file modifications.
|
||
We will add a provider, modal overlay, emulator player component, and update the sidebar menu and root layout to integrate the overlay trigger and mount point.
|
||
|
||
Detailed breakdown:
|
||
- New files to be created (with full paths and purpose)
|
||
1) app/providers/DoomOverlayProvider.tsx
|
||
- Purpose: Client-side React context that exposes open/close state and actions for the Doom modal. Wraps children so any component can open the overlay.
|
||
2) app/components/overlay/Modal.tsx
|
||
- Purpose: Accessible, reusable modal component using a portal, focus trap, ESC-to-close, backdrop click-to-close, and scroll lock.
|
||
3) app/components/doom/DoomOverlay.tsx
|
||
- Purpose: The top-level container rendered inside the modal. It displays loading/error states and hosts the emulator player. Includes a close button and optional instructions if remote loading fails.
|
||
4) app/components/doom/JsDosPlayer.tsx
|
||
- Purpose: Client-only component that lazy-loads “js-dos”, renders the emulator surface, runs a provided .jsdos archive via a URL, and tears down cleanly on unmount.
|
||
- Existing files to be modified (with specific changes)
|
||
1) app/layout.tsx
|
||
- Wrap current body content with <DoomOverlayProvider>. Mount <DoomOverlay /> near the end of <body> (still inside providers) so it renders above app content, but shares styles/providers.
|
||
2) app/components/sidebar-menu.tsx
|
||
- Add a new “running on a potato” menu action above or below current items (About/Writing/Contact).
|
||
- This should be a button (not a Link) that calls open() from DoomOverlayProvider to display the modal. Maintain current styling conventions via cn() and Tailwind.
|
||
- Files to be deleted or moved
|
||
- None.
|
||
- Configuration file updates
|
||
- package.json (Dependencies): add "js-dos".
|
||
- No changes to next.config.ts or tsconfig.json are required for this plan.
|
||
|
||
[Functions]
|
||
Single sentence describing function modifications.
|
||
We will add modal state management functions, emulator lifecycle controls, and integrate a new onclick handler in the sidebar menu.
|
||
|
||
Detailed breakdown:
|
||
- New functions (name, signature, file path, purpose)
|
||
1) open ((): void) — app/providers/DoomOverlayProvider.tsx
|
||
- Purpose: Set modal open state to true.
|
||
2) close ((): void) — app/providers/DoomOverlayProvider.tsx
|
||
- Purpose: Set modal open state to false.
|
||
3) DoomOverlay ((): JSX.Element) — app/components/doom/DoomOverlay.tsx
|
||
- Purpose: Renders Modal and inside it lazy-loads the emulator (JsDosPlayer). Handles errors, shows loading fallback, exposes close control.
|
||
4) JsDosPlayer ((props: JsDosPlayerProps) => JSX.Element) — app/components/doom/JsDosPlayer.tsx
|
||
- Purpose: Initializes js-dos in a container div/canvas and runs a .jsdos ZIP archive via props.zipUrl. Disposes emulator instance on unmount.
|
||
5) Modal ((props: { open: boolean; onClose: () => void; children: React.ReactNode; title?: string; className?: string }) => JSX.Element) — app/components/overlay/Modal.tsx
|
||
- Purpose: Accessible modal with portal, focus trap, backdrop, escape close, body scroll lock.
|
||
- Modified functions (exact name, current file path, required changes)
|
||
1) default export SidebarMenu — app/components/sidebar-menu.tsx
|
||
- Add one extra item rendered as a button:
|
||
- Label: "running on a potato"
|
||
- onClick: calls open() via DoomOverlayProvider context
|
||
- Style: consistent with other menu items (Tailwind classes), ensure focus-visible styles for accessibility.
|
||
- Removed functions (name, file path, reason, migration strategy)
|
||
- None.
|
||
|
||
[Classes]
|
||
Single sentence describing class modifications.
|
||
No class-based components are used; all additions are functional React components.
|
||
|
||
Detailed breakdown:
|
||
- New classes
|
||
- None.
|
||
- Modified classes
|
||
- None.
|
||
- Removed classes
|
||
- None.
|
||
|
||
[Dependencies]
|
||
Single sentence describing dependency modifications.
|
||
We will add js-dos to support running Doom in-browser via a WASM-powered DOSBox.
|
||
|
||
Details of new packages, version changes, and integration requirements.
|
||
- Add: "js-dos": "^7.5.0" (or latest v7)
|
||
- Import usage in client-only component:
|
||
- import "js-dos/css/js-dos.css"
|
||
- const { Dos } = await import("js-dos")
|
||
- Runtime:
|
||
- Provide a container element; initialize: const dos = Dos(container)
|
||
- Run: dos.run(zipUrl)
|
||
- No special COOP/COEP headers required for basic usage.
|
||
- Asset loading:
|
||
- Default: remote demo archive (subject to availability), e.g. https://v8.js-dos.com/v7/build/doom.jsdos
|
||
- Fallback: file input to select a local .jsdos package. We will show instructions in the overlay if remote load fails.
|
||
- No changes to Next.js, React, Tailwind, or other existing dependencies.
|
||
|
||
[Testing]
|
||
Single sentence describing testing approach.
|
||
We will verify overlay behavior, emulator load success and teardown, and ensure no SSR/runtime errors across pages.
|
||
|
||
Test file requirements, existing test modifications, and validation strategies.
|
||
- Manual QA (dev server):
|
||
- Open site, click “running on a potato” in sidebar:
|
||
- Modal opens, background scroll locked, focus lands on modal.
|
||
- Emulator loads and becomes interactive; performance acceptable.
|
||
- Close via ESC, backdrop click (if enabled), or close button — focus returns to triggering button.
|
||
- Network-offline test:
|
||
- Remote .jsdos load should fail gracefully; overlay shows a message and a file picker to choose a local .jsdos archive. After selecting, emulator runs.
|
||
- Navigation test:
|
||
- Navigate between sections while modal closed; ensure global provider doesn’t interfere with Lenis/scroll.
|
||
- Accessibility:
|
||
- Confirm appropriate aria attributes, focus management, and keyboard support.
|
||
- Performance:
|
||
- Confirm dynamic import defers js-dos until modal opens.
|
||
- Confirm emulator stops and frees resources when modal closes.
|
||
|
||
[Implementation Order]
|
||
Single sentence describing the implementation sequence.
|
||
Implement provider and modal primitives first, then the overlay and emulator, finally wire up the sidebar, and add the dependency.
|
||
|
||
Numbered steps showing the logical order of changes to minimize conflicts and ensure successful integration.
|
||
1) Add dependency: js-dos to package.json and install.
|
||
2) Create app/providers/DoomOverlayProvider.tsx with context, open/close, and a useDoomOverlay() hook.
|
||
3) Create app/components/overlay/Modal.tsx with portal, focus trap, ESC handling, backdrop, and scroll lock.
|
||
4) Create app/components/doom/JsDosPlayer.tsx that:
|
||
- Dynamically imports "js-dos" on mount (client-only).
|
||
- Imports "js-dos/css/js-dos.css".
|
||
- Initializes Dos(container) and calls .run(zipUrl).
|
||
- Cleans up on unmount or when parent closes.
|
||
5) Create app/components/doom/DoomOverlay.tsx that:
|
||
- Reads overlay state from context.
|
||
- Uses Modal to render content.
|
||
- Attempts to load remote .jsdos: https://v8.js-dos.com/v7/build/doom.jsdos
|
||
- If loading fails, renders a file input and drag-and-drop area to run a local .jsdos file.
|
||
- Provides close control UI.
|
||
6) Modify app/layout.tsx to wrap existing providers with <DoomOverlayProvider> and mount <DoomOverlay /> near the end of <body>.
|
||
7) Modify app/components/sidebar-menu.tsx to add a new button-styled item labeled “running on a potato” that calls open() from the provider.
|
||
8) Run dev server and test:
|
||
- Modal open/close behavior.
|
||
- Emulator loads, runs, and shuts down cleanly.
|
||
- Accessibility and performance checks.
|
||
9) Optional polish:
|
||
- Add loading spinner.
|
||
- Persist last used source (remote vs local) in session storage.
|
||
- Add instructions link about providing your own .jsdos archive if desired.
|