Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ docs/superpowers/
# Local editor and tooling metadata
.cursor/
.claude/
AGENTS.md
/AGENTS.md
.cursorrules
159 changes: 87 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,111 @@
# namefailed.github.io

**Live:** https://mrgrey.site
**Alt:** https://namefailed.github.io/
**Live:** [mrgrey.site](https://mrgrey.site) · **Alt:** [namefailed.github.io](https://namefailed.github.io/)

A personal portfolio built as an in-browser window manager — tiling layout, xterm.js terminal, a toy filesystem, seven colour themes, and optional CRT/matrix effects.
A personal portfolio built as an **in-browser desktop OS** — tiling window manager, xterm.js terminal, virtual filesystem, vim-style editor, interactive demos, and seven runtime colour themes. The same content also ships as a polished brochure at `/static/` (mobile default).

A second entry (`/static/`) serves the same portfolio content as a polished brochure page. Mobile visitors are redirected there automatically.
---

## Stack
## At a glance

- TypeScript, Vite 8 (two HTML entry points)
- [@xterm/xterm](https://github.com/xtermjs/xterm.js) + fit + web-links addons
- Three.js (Rubik's cube — lazy-loaded in its own chunk)
- Web Audio API (sound effects)
- No framework — vanilla DOM throughout
| | |
|---|---|
| **Stack** | TypeScript · Vite 8 · vanilla DOM (no framework) |
| **Terminal** | [@xterm/xterm](https://github.com/xtermjs/xterm.js) + vim input layer |
| **3D** | Three.js (Rubik cube — lazy chunk only) |
| **Tests** | **563** unit · **3** e2e smoke · CI on every `main` push |
| **Deploy** | GitHub Actions → GitHub Pages (`dist/`) |

---

## Why this exists

Most portfolios list skills. This one **runs** them: modular TypeScript, lazy code splitting, keyboard-driven UX, tested pure helpers, and client-side persistence — packaged as something memorable to explore.

**For reviewers:** start with [docs/OVERVIEW.md](docs/OVERVIEW.md) — architecture diagram, technical highlights, skills map.

**For contributors & AI agents:** [docs/README.md](docs/README.md) — full documentation index.

---

## Quick start

```bash
npm install
npm run dev # desktop → http://localhost:5173/
npm test # 563 unit tests
npm run build && npm run test:e2e
```

Open the terminal (`Ctrl+T`) and type `help`.

---

## Features

### Desktop shell
- Tiling window manager with floating dock and launcher overlay
- xterm.js terminal with vim-mode editing (insert / normal / visual)
- Seven switchable colour themes — Catppuccin Mocha, Dracula, Nord, Gruvbox Dark, Tokyo Night, Solarized Dark, One Dark
- Matrix rain backdrop and CRT scanline/vignette overlay (both toggleable and persistent)
- Virtual filesystem (VFS v8) backed by `localStorage` — `cat`, `ls`, `mkdir`, `touch`, `rm`, `mv`, `cp`, `edit`, `wc`, and more

### Shell commands
- `resume` — full résumé with inline skills matrix in ANSI colour
- `projects` — portfolio project listing with links
- `contact`, `about`, `help`, `whoami`, `motd`, `fortune`
- `theme [id|list|random]` — switch colour packs at runtime
- `browse <url>` — embedded browser tile
- `edit [file]` — in-shell text editor backed by the VFS; `F5` / `:run` plays a `.js` file in the p5 viewer
- `p5` — p5.js sketch viewer; 8+ built-in sketches; `Open…` loads from VFS
- `cube` — interactive Rubik's cube (Three.js); drag to spin, U/D/L/R/F/B keys, animated scramble/solve, algorithm picker
- `snake`, `pong` — playable games
- `paint` — pixel canvas
- `ssh`, `apt`, `cowsay`, `neofetch`, `wc`, `matrix` — easter eggs and flavour commands

### p5.js sketches
Pre-loaded in `~/sketches/` (VFS):
- Fractal Tree, Game of Life, Lorenz Attractor, Spirograph, Noise Terrain, Mandelbrot, Bouncing Balls, Sine Waves
- Create your own: `edit ~/sketches/myscript.js` → `F5` to run it live

### Static brochure (`/static/`)
- Scroll progress bar, floating section-nav dots with tooltips
- Typewriter headline effect, animated stat counters
- Scroll-triggered fade-in animations (respects `prefers-reduced-motion`)
- Experience cards with role-type colour strips and a "Featured" badge
- Auto-redirect from mobile: viewport ≤ 768px lands on `/static/` by default

### Keybinds
- BSP tiling window manager with drag splitters, floating dock, Applications launcher
- Lazy-loaded tiles: résumé, projects, editor, file explorer, browser, p5 viewer, games, Rubik cube
- xterm.js shell with 50+ commands and vim-style prompt editing
- VFS v8 in `localStorage` — real `edit` / `ls` / `mkdir` workflow
- Seven themes, CRT overlay, matrix rain, wallpaper, Web Audio UI sounds

### Brochure (`/static/`)
- Scroll progress, section nav, typewriter hero, animated counters
- Auto-redirect on viewport ≤768px

---

## Keyboard shortcuts

| Chord | Action |
|-------|--------|
| `Ctrl+T` | Open / focus terminal |
| `Ctrl+D` | Desktop / launcher |
| `Ctrl+H` | Focus terminal (← left) |
| `Ctrl+L` | Enter right pane (→) |
| `Ctrl+K` | Previous window (↑) |
| `Ctrl+J` | Next window (↓) |
| `Ctrl+Q` | Close focused window |
| `Ctrl+M` | Minimise focused window |
| `Ctrl+F` | Maximise / restore |
| `Ctrl+1–9` | Focus Nth open window |
| `Ctrl+D` | Applications launcher |
| `Ctrl+H/L/K/J` | Focus window ← → ↑ ↓ |
| `Ctrl+Q/M/F` | Close / minimize / maximize |
| `Ctrl+1–9` | Focus dock slot |

Full list: `keybinds` in terminal or [docs/USER_GUIDE.md](docs/USER_GUIDE.md).

---

## Documentation

| Document | Audience |
|----------|----------|
| [docs/README.md](docs/README.md) | **Documentation hub** |
| [docs/OVERVIEW.md](docs/OVERVIEW.md) | Employers & technical reviewers |
| [docs/USER_GUIDE.md](docs/USER_GUIDE.md) | Site users |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Module layout & data flow |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Local dev & change recipes |
| [docs/AGENTS.md](docs/AGENTS.md) | AI agents & automation |
| [docs/API.md](docs/API.md) | Types & extension APIs |
| [docs/THEMING.md](docs/THEMING.md) | Colour packs & CSS tokens |
| [docs/STYLE_GUIDE.md](docs/STYLE_GUIDE.md) | Coding standards |

---

## Scripts

| Command | Description |
|---------|-------------|
| `npm run dev` | Vite dev server — desktop at `/`, brochure at `/static/` |
| `npm run build` | `tsc` then Vite build → `dist/` and `dist/static/` |
| `npm run preview` | Preview `dist/` locally |
| `npm test` | Vitest — `*.test.ts` files |
| `npm run test:coverage` | Vitest with v8 coverage report |
| `npm run lint` | ESLint (TypeScript + e2e) |
| `npm run test:e2e` | Playwright smoke tests against `dist/` preview |
| `npm run dev` | Vite dev `/` desktop, `/static/` brochure |
| `npm run build` | `tsc` + Vite → `dist/` |
| `npm run preview` | Preview production build |
| `npm test` | Vitest unit suite |
| `npm run test:coverage` | Coverage report |
| `npm run lint` | ESLint |
| `npm run test:e2e` | Playwright smoke tests |

## GitHub Pages
---

The deploy publishes `dist/`, not the repo root. `index.html` at the root only works under `vite dev`.

**One-time setup:** repo → Settings → Pages → Build and deployment → Source → **GitHub Actions**. Pushes to `main` build and deploy via `.github/workflows/deploy-pages.yml`.
## GitHub Pages

## Docs
Deploy publishes **`dist/`**, not the repo root. One-time: repo → Settings → Pages → Source → **GitHub Actions**. Pushes to `main` run lint, tests, build, e2e, then deploy (`.github/workflows/deploy-pages.yml`).

- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — module layout, bootstrap order, chunk strategy, testing
- [docs/THEMING.md](docs/THEMING.md) — ThemePack interface, custom property reference, how to add a pack
- [docs/STYLE_GUIDE.md](docs/STYLE_GUIDE.md) — TypeScript standards, CSS conventions, testing guidelines
- [docs/API.md](docs/API.md) — Window system, VFS, storage utilities, theming API
---

## Testing
## License

428 unit tests across 27 test files (plus Playwright smoke e2e).
- `npm test` — run Vitest suite
- Tests co-located with source: `module.ts` → `module.test.ts`
- Coverage: VFS, vim input, storage, ANSI, CLI tools, window chrome, matrix rain, boot splash, rubik model, p5 sketches, launcher catalog, desktop tiles, desktop WM (focus/keyboard), static motion, intro toasts, hint bubbles, wallpaper, first-visit flags, BSP layout, theme control
Personal portfolio — source available for review; contact author for reuse.
205 changes: 205 additions & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Agent Guide

Machine-oriented reference for AI coding agents, automation, and contributors who need **deterministic navigation** of this repository.

---

## Mission

Build and maintain an in-browser fake desktop portfolio. Primary constraints:

1. **No UI framework** — vanilla TypeScript + DOM.
2. **Lazy tiles** — dynamic `import()` for every `*-window.ts` except `appwindow.ts`.
3. **Never move iframe-backed DOM nodes** after mount (p5, browser tiles reload if reparented).
4. **Persist via `storage.ts`** — handle private-mode failure gracefully.
5. **Test pure logic** in Node; stub DOM when testing layout/WM.

---

## Entry-point graph

```
index.html
└─ src/main.ts
└─ bootstrap-shell.ts
├─ boot-splash.ts
├─ theme-control.ts (initThemeFromStorage)
├─ retro-fx.ts, os-sound.ts, os-systray.ts
├─ matrix-bg.ts (idle)
└─ new Desktop(#desktop)
├─ bsp-layout.ts (#right-pane)
├─ desktop-open-window.ts (lazy tiles)
└─ terminal.ts (lazy tile via openWindow)

static/index.html
└─ src/static/main.ts (brochure only — no desktop imports)
```

---

## File → responsibility map

| Path pattern | Role |
|--------------|------|
| `desktop.ts` | WM orchestrator — do not re-bloat; extract to `desktop-*.ts` |
| `desktop-wm-*.ts` | WM subsystems (lifecycle, maximize, sync, focus, …) |
| `desktop-open-window.ts` | **Single dispatch** for opening tiles |
| `desktop-window-spec.ts` | Build `WindowSpec` from command strings |
| `launcher-catalog.ts` | Dock pins, launcher grid, prefetch map |
| `*-window.ts` | Tile UI class (`el`, `command`, WM callbacks) |
| `terminal.ts` | xterm + command execution + `openWindow` bridge |
| `commands/index.ts` | Shell command registry merge point |
| `commands/app-commands.ts` | Tile stubs returning `[]` |
| `os-fs.ts` | VFS — key `portfolio-vfs-v8-namefailed-home` |
| `os-registry.ts` | Breaks circular import: terminal → desktop ref |
| `editor-vim-ops.ts` | Pure editor motions — **preferred home for new vim text helpers** |
| `editor-vim-keys.ts` | Pure editor key-chord helpers |
| `vim.ts` | Terminal one-line vim widget (separate from editor tile) |
| `bsp-layout.ts` | Two-column BSP + splitters |
| `splitter.ts` | Pointer drag resize |
| `theme-packs.ts` | All `--th-*` values per theme |
| `content/copy/*.ts` | Portfolio text sources |
| `portfolio.ts` | ANSI line arrays for content tiles |

---

## Critical invariants

| Invariant | Why |
|-----------|-----|
| `app-commands` handlers return `[]` | Desktop opens tiles; terminal must not print fake output |
| `setDesktopRef(this)` in Desktop ctor | Terminal `openWindow` routing |
| `TERMINAL_TILE_SENTINEL = '__terminal__'` | Dock/focus id for terminal tile |
| `focusedId === null` | No right-pane tile focused (not “legacy terminal focused”) |
| `#panes` contains only `#right-pane` | Terminal is a tile, not static HTML column |
| BSP `maxVisible = 4` | Fifth tile bumps oldest to dock |
| Editor buffer mutations | Prefer `editor-vim-ops.ts` + tests before touching `editor-window.ts` |

---

## Change recipes

### New shell command (no tile)

```
commands/<subsystem>-commands.ts → add Command entry
commands/<subsystem>-commands.test.ts → add test
commands/index.ts → already spreads submodule
```

### New tile command `myapp`

```
1. src/myapp-window.ts — tile class
2. desktop-open-window.ts — case in dispatchOpenWindow + dynamic import
3. commands/app-commands.ts — myapp: { run: () => [], loadMs: N }
4. launcher-catalog.ts — TILED_WINDOW_COMMANDS, LAUNCHER_ICON_ROWS, prefetch
5. desktop-window-spec.ts — if portfolio spec needed
6. tests for pure helpers only
```

### WM behaviour change

Prefer editing the extracted module, not `desktop.ts`:

| Concern | Module |
|---------|--------|
| Close/minimize animation | `desktop-wm-lifecycle.ts` |
| Maximize | `desktop-wm-maximize.ts` |
| Ctrl+chords | `desktop-keyboard-handler.ts` + `desktop-keyboard-chords.ts` |
| H/J/K/L focus | `desktop-spatial-focus.ts` |
| Shell CSS dataset | `desktop-wm-sync.ts` |
| Host bindings | `desktop-wm-hosts.ts` |

### Editor vim motion

```
1. Add pure function to editor-vim-ops.ts
2. Test in editor-vim-ops.test.ts
3. Wire one-liner in editor-window.ts
```

---

## Test commands

```bash
npm test # all unit tests
npm test -- src/foo.test.ts # single file
npm run lint
npm run build
npm run test:e2e # needs dist + playwright chromium
```

Vitest config: `vite.config.ts` → `test.environment: 'node'`.

WM tests often use `FakeEl` trees — copy pattern from `src/desktop.test.ts` or `src/bsp-layout.test.ts`.

---

## localStorage keys (authoritative)

| Key | Module | Purpose |
|-----|--------|---------|
| `portfolio-vfs-v8-namefailed-home` | `os-fs.ts` | VFS JSON state |
| `mrgrey-theme` | `theme-control.ts` | Active theme id |
| `mrgrey-os-sound` | `os-sound.ts` | Sound enabled |
| `mrgrey-os-volume` | `os-sound.ts` | Volume 0–1 |
| `mrgrey-retro-fx` | `retro-fx.ts` | CRT overlay |
| `mrgrey-matrix-bg` | `matrix-bg.ts` | Matrix rain on/off |
| `mrgrey-wallpaper` | `wallpaper.ts` | Wallpaper URL |
| `mrgrey-desktop-tile-positions-v6` | `desktop-tiles.ts` | Folder tile drag layout |
| `portfolio-fe-prefs-v1` | `file-explorer-window.ts` | Explorer prefs |
| `mrgrey-browser-iframe-tip-dismiss` | `browser-window.ts` | Browser tip permanent dismiss |
| `mrgrey-boot-seen` | `boot-splash.ts` | Skip boot animation |
| `mrgrey-guide-seen` | `welcome-guide.ts` | Onboarding guide |
| `mrgrey-toasts-seen` | `intro-toasts.ts` | Intro toast sequence |
| `mrgrey-hint-<id>` | `hint-bubbles.ts` | Per-tile hint dismissal |
| `mrgrey-p5-tip-seen` | `p5-window.ts` | p5 viewer tip |
| `mrgrey-pkgs-v1` | `os-packages.ts` | Installed joke packages |
| `mrgrey-apt-cowsay` | `os-apt.ts` | cowsay install flag |

Always read/write through `storage.ts` helpers.

---

## Custom events

| Event | When |
|-------|------|
| `mrgrey-theme-change` | Theme applied |
| `mrgrey-wallpaper-change` | Wallpaper set/cleared |
| `mrgrey-first-window` | First tile opened (onboarding) |
| `mrgrey-terminal-cmd` | First terminal command run |

---

## Docs to update when you change…

| Change | Update |
|--------|--------|
| New module / layer | `docs/ARCHITECTURE.md` table |
| New storage key | `docs/ARCHITECTURE.md` + this file |
| New command / keybind | `docs/USER_GUIDE.md` + `help-output.ts` |
| New theme | `docs/THEMING.md` pack table |
| Public API / types | `docs/API.md` |
| Test count shift | `docs/README.md`, root `README.md` |

---

## Anti-patterns

- Adding static `#terminal-window` HTML — terminal is **lazy tile only**
- Importing `desktop.ts` from tiles — use callbacks + `os-registry.ts`
- Large new features entirely inside `desktop.ts` — extract module + tests
- Raw `localStorage.setItem` — use `storage.ts`
- Hard-coded colours — use `--th-*` from theme packs
- DOM-heavy tests without stubs — will fail in Node Vitest

---

## See also

- [DEVELOPMENT.md](./DEVELOPMENT.md) — human setup guide
- [ARCHITECTURE.md](./ARCHITECTURE.md) — full module catalogue
- [API.md](./API.md) — type definitions
Loading
Loading