Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
d791c087cb feat: add Ezra mempalace integration packet (#570)
Some checks failed
Smoke Test / smoke (pull_request) Failing after 22s
2026-04-15 01:37:47 -04:00
5 changed files with 319 additions and 574 deletions

View File

@@ -0,0 +1,92 @@
# MemPalace v3.0.0 — Ezra Integration Packet
This packet turns issue #570 into an executable, reviewable integration plan for Ezra's Hermes home.
It is a repo-side scaffold: no live Ezra host changes are claimed in this artifact.
## Commands
```bash
pip install mempalace==3.0.0
mempalace init ~/.hermes/ --yes
cat > ~/.hermes/mempalace.yaml <<'YAML'
wing: ezra_home
palace: ~/.mempalace/palace
rooms:
- name: sessions
description: Conversation history and durable agent transcripts
globs:
- "*.json"
- "*.jsonl"
- name: config
description: Hermes configuration and runtime settings
globs:
- "*.yaml"
- "*.yml"
- "*.toml"
- name: docs
description: Notes, markdown docs, and operating reports
globs:
- "*.md"
- "*.txt"
people: []
projects: []
YAML
echo "" | mempalace mine ~/.hermes/
echo "" | mempalace mine ~/.hermes/sessions/ --mode convos
mempalace search "your common queries"
mempalace wake-up
hermes mcp add mempalace -- python -m mempalace.mcp_server
```
## Manual config template
```yaml
wing: ezra_home
palace: ~/.mempalace/palace
rooms:
- name: sessions
description: Conversation history and durable agent transcripts
globs:
- "*.json"
- "*.jsonl"
- name: config
description: Hermes configuration and runtime settings
globs:
- "*.yaml"
- "*.yml"
- "*.toml"
- name: docs
description: Notes, markdown docs, and operating reports
globs:
- "*.md"
- "*.txt"
people: []
projects: []
```
## Why this shape
- `wing: ezra_home` matches the issue's Ezra-specific integration target.
- `rooms` split the mined material into sessions, config, and docs to keep retrieval interpretable.
- Mining commands pipe empty stdin to avoid the interactive entity-detector hang noted in the evaluation.
## Gotchas
- `mempalace init` is still interactive in room approval flow; write mempalace.yaml manually if the init output stalls.
- The yaml key is `wing:` not `wings:`. Using the wrong key causes mine/setup failures.
- Pipe empty stdin into mining commands (`echo "" | ...`) to avoid the entity-detector stdin hang on larger directories.
- First mine downloads the ChromaDB embedding model cache (~79MB).
- Report Ezra's before/after metrics back to issue #568 after live installation and retrieval tests.
## Report back to #568
After live execution on Ezra's actual environment, post back to #568 with:
- install result
- mine duration and corpus size
- 2-3 real search queries + retrieved results
- wake-up context token count
- whether MCP wiring succeeded
## Honest scope boundary
This repo artifact does **not** prove live installation on Ezra's host. It makes the work reproducible and testable so the next pass can execute it without guesswork.

View File

@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Prepare a MemPalace v3.0.0 integration packet for Ezra's Hermes home."""
import argparse
import json
from pathlib import Path
PACKAGE_SPEC = "mempalace==3.0.0"
DEFAULT_HERMES_HOME = "~/.hermes/"
DEFAULT_SESSIONS_DIR = "~/.hermes/sessions/"
DEFAULT_PALACE_PATH = "~/.mempalace/palace"
DEFAULT_WING = "ezra_home"
def build_yaml_template(wing: str, palace_path: str) -> str:
return (
f"wing: {wing}\n"
f"palace: {palace_path}\n"
"rooms:\n"
" - name: sessions\n"
" description: Conversation history and durable agent transcripts\n"
" globs:\n"
" - \"*.json\"\n"
" - \"*.jsonl\"\n"
" - name: config\n"
" description: Hermes configuration and runtime settings\n"
" globs:\n"
" - \"*.yaml\"\n"
" - \"*.yml\"\n"
" - \"*.toml\"\n"
" - name: docs\n"
" description: Notes, markdown docs, and operating reports\n"
" globs:\n"
" - \"*.md\"\n"
" - \"*.txt\"\n"
"people: []\n"
"projects: []\n"
)
def build_plan(overrides: dict | None = None) -> dict:
overrides = overrides or {}
hermes_home = overrides.get("hermes_home", DEFAULT_HERMES_HOME)
sessions_dir = overrides.get("sessions_dir", DEFAULT_SESSIONS_DIR)
palace_path = overrides.get("palace_path", DEFAULT_PALACE_PATH)
wing = overrides.get("wing", DEFAULT_WING)
yaml_template = build_yaml_template(wing=wing, palace_path=palace_path)
config_home = hermes_home[:-1] if hermes_home.endswith("/") else hermes_home
plan = {
"package_spec": PACKAGE_SPEC,
"hermes_home": hermes_home,
"sessions_dir": sessions_dir,
"palace_path": palace_path,
"wing": wing,
"config_path": f"{config_home}/mempalace.yaml",
"install_command": f"pip install {PACKAGE_SPEC}",
"init_command": f"mempalace init {hermes_home} --yes",
"mine_home_command": f"echo \"\" | mempalace mine {hermes_home}",
"mine_sessions_command": f"echo \"\" | mempalace mine {sessions_dir} --mode convos",
"search_command": 'mempalace search "your common queries"',
"wake_up_command": "mempalace wake-up",
"mcp_command": "hermes mcp add mempalace -- python -m mempalace.mcp_server",
"yaml_template": yaml_template,
"gotchas": [
"`mempalace init` is still interactive in room approval flow; write mempalace.yaml manually if the init output stalls.",
"The yaml key is `wing:` not `wings:`. Using the wrong key causes mine/setup failures.",
"Pipe empty stdin into mining commands (`echo \"\" | ...`) to avoid the entity-detector stdin hang on larger directories.",
"First mine downloads the ChromaDB embedding model cache (~79MB).",
"Report Ezra's before/after metrics back to issue #568 after live installation and retrieval tests.",
],
}
return plan
def render_markdown(plan: dict) -> str:
gotchas = "\n".join(f"- {item}" for item in plan["gotchas"])
return f"""# MemPalace v3.0.0 — Ezra Integration Packet
This packet turns issue #570 into an executable, reviewable integration plan for Ezra's Hermes home.
It is a repo-side scaffold: no live Ezra host changes are claimed in this artifact.
## Commands
```bash
{plan['install_command']}
{plan['init_command']}
cat > {plan['config_path']} <<'YAML'
{plan['yaml_template'].rstrip()}
YAML
{plan['mine_home_command']}
{plan['mine_sessions_command']}
{plan['search_command']}
{plan['wake_up_command']}
{plan['mcp_command']}
```
## Manual config template
```yaml
{plan['yaml_template'].rstrip()}
```
## Why this shape
- `wing: {plan['wing']}` matches the issue's Ezra-specific integration target.
- `rooms` split the mined material into sessions, config, and docs to keep retrieval interpretable.
- Mining commands pipe empty stdin to avoid the interactive entity-detector hang noted in the evaluation.
## Gotchas
{gotchas}
## Report back to #568
After live execution on Ezra's actual environment, post back to #568 with:
- install result
- mine duration and corpus size
- 2-3 real search queries + retrieved results
- wake-up context token count
- whether MCP wiring succeeded
## Honest scope boundary
This repo artifact does **not** prove live installation on Ezra's host. It makes the work reproducible and testable so the next pass can execute it without guesswork.
"""
def main() -> None:
parser = argparse.ArgumentParser(description="Prepare the MemPalace Ezra integration packet")
parser.add_argument("--hermes-home", default=DEFAULT_HERMES_HOME)
parser.add_argument("--sessions-dir", default=DEFAULT_SESSIONS_DIR)
parser.add_argument("--palace-path", default=DEFAULT_PALACE_PATH)
parser.add_argument("--wing", default=DEFAULT_WING)
parser.add_argument("--output", default=None)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
plan = build_plan(
{
"hermes_home": args.hermes_home,
"sessions_dir": args.sessions_dir,
"palace_path": args.palace_path,
"wing": args.wing,
}
)
rendered = json.dumps(plan, indent=2) if args.json else render_markdown(plan)
if args.output:
output_path = Path(args.output).expanduser()
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(rendered, encoding="utf-8")
print(f"MemPalace integration packet written to {output_path}")
else:
print(rendered)
if __name__ == "__main__":
main()

View File

@@ -1,35 +0,0 @@
from pathlib import Path
def _content() -> str:
return Path("the-playground-GENOME.md").read_text()
def test_the_playground_genome_exists() -> None:
assert Path("the-playground-GENOME.md").exists()
def test_the_playground_genome_has_required_sections() -> None:
content = _content()
assert "# GENOME.md — the-playground" in content
assert "## Project Overview" in content
assert "## Architecture" in content
assert "```mermaid" in content
assert "## Entry Points" in content
assert "## Data Flow" in content
assert "## Key Abstractions" in content
assert "## API Surface" in content
assert "## Test Coverage Gaps" in content
assert "## Security Considerations" in content
assert "## Dependencies" in content
assert "## Deployment" in content
assert "## Technical Debt" in content
def test_the_playground_genome_captures_repo_specific_findings() -> None:
content = _content()
assert "IndexedDB" in content
assert "AudioContext" in content
assert "smoke-test.html" in content
assert "no tests ran" in content
assert "innerHTML" in content

View File

@@ -0,0 +1,68 @@
from pathlib import Path
import importlib.util
import unittest
ROOT = Path(__file__).resolve().parent.parent
SCRIPT_PATH = ROOT / "scripts" / "mempalace_ezra_integration.py"
DOC_PATH = ROOT / "docs" / "MEMPALACE_EZRA_INTEGRATION.md"
def load_module(path: Path, name: str):
assert path.exists(), f"missing {path.relative_to(ROOT)}"
spec = importlib.util.spec_from_file_location(name, path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class TestMempalaceEzraIntegration(unittest.TestCase):
def test_build_plan_contains_issue_required_steps_and_gotchas(self):
mod = load_module(SCRIPT_PATH, "mempalace_ezra_integration")
plan = mod.build_plan({})
self.assertEqual(plan["package_spec"], "mempalace==3.0.0")
self.assertIn("pip install mempalace==3.0.0", plan["install_command"])
self.assertEqual(plan["wing"], "ezra_home")
self.assertIn('echo "" | mempalace mine ~/.hermes/', plan["mine_home_command"])
self.assertIn('--mode convos', plan["mine_sessions_command"])
self.assertIn('mempalace wake-up', plan["wake_up_command"])
self.assertIn('hermes mcp add mempalace -- python -m mempalace.mcp_server', plan["mcp_command"])
self.assertIn('wing:', plan["yaml_template"])
self.assertTrue(any('stdin' in item.lower() for item in plan["gotchas"]))
self.assertTrue(any('wing:' in item for item in plan["gotchas"]))
def test_build_plan_accepts_path_and_wing_overrides(self):
mod = load_module(SCRIPT_PATH, "mempalace_ezra_integration")
plan = mod.build_plan(
{
"hermes_home": "/root/wizards/ezra/home",
"sessions_dir": "/root/wizards/ezra/home/sessions",
"wing": "ezra_archive",
}
)
self.assertEqual(plan["wing"], "ezra_archive")
self.assertIn('/root/wizards/ezra/home', plan["mine_home_command"])
self.assertIn('/root/wizards/ezra/home/sessions', plan["mine_sessions_command"])
self.assertIn('wing: ezra_archive', plan["yaml_template"])
def test_repo_contains_mem_palace_ezra_doc(self):
self.assertTrue(DOC_PATH.exists(), "missing committed MemPalace Ezra integration doc")
text = DOC_PATH.read_text(encoding="utf-8")
required = [
"# MemPalace v3.0.0 — Ezra Integration Packet",
"pip install mempalace==3.0.0",
'echo "" | mempalace mine ~/.hermes/',
"mempalace mine ~/.hermes/sessions/ --mode convos",
"mempalace wake-up",
"hermes mcp add mempalace -- python -m mempalace.mcp_server",
"Report back to #568",
]
for snippet in required:
self.assertIn(snippet, text)
if __name__ == "__main__":
unittest.main()

View File

@@ -1,539 +0,0 @@
# GENOME.md — the-playground
Generated: 2026-04-15 00:19:15 EDT
Repo: Timmy_Foundation/the-playground
Issue: timmy-home #671
## Project Overview
The Sovereign Playground is a browser-only creative sandbox: a dark, local-first art toy with an entrance ritual, a canvas in the center, a sound panel on the left, a gallery on the right, and a footer action bar for save/download/clear/fullscreen.
The current codebase is much smaller than the README vision. The README describes a platform with Sound Studio, Visual Forge, Gallery, Games Floor, Video Forge, and a long roadmap of immersive experiences. The code on `main` today implements a solid prototype shell with:
- a cinematic entrance screen
- two actual canvas modes: `free-draw` and `ambient`
- a basic Web Audio engine for notes/chords/scales
- a basic Canvas 2D visual engine
- an IndexedDB-backed gallery
- a manual browser smoke harness
Quick measured facts from the fresh main clone I analyzed:
- 10 JavaScript source files
- 1 CSS design system file
- 2 HTML entry pages (`index.html`, `smoke-test.html`)
- 0 package manifests
- 0 build steps
- `python3 -m pytest -q` -> `no tests ran in 0.02s`
- browser smoke harness shows 18 passing checks, but the summary is broken and still says `0 passed, 0 failed`
This repo is best understood as a browser-native prototype platform shell with one strong design language and three real cores:
1. orchestration in `src/playground.js`
2. browser engines (`PlaygroundAudio`, `PlaygroundVisual`, `PlaygroundGallery`)
3. thin shared globals (`PlaygroundUtils`, `PlaygroundState`, `PlaygroundEvents`, `ModeManager`)
## Architecture
```mermaid
graph TD
HTML[index.html] --> CSS[src/styles/design-system.css]
HTML --> U[src/utils/utils.js]
HTML --> S[src/utils/state.js]
HTML --> E[src/utils/events.js]
HTML --> A[src/engine/audio-engine.js]
HTML --> V[src/engine/visual-engine.js]
HTML --> G[src/gallery/gallery.js]
HTML --> SP[src/panels/sound/sound-panel.js]
HTML --> GP[src/panels/gallery/gallery-panel.js]
HTML --> M[src/modes/mode-manager.js]
HTML --> P[src/playground.js]
P --> A
P --> V
P --> G
P --> SP
P --> GP
P --> M
P --> S
P --> E
P --> U
User[User interactions] --> P
P --> Canvas[Canvas 2D]
P --> Audio[AudioContext]
P --> DB[IndexedDB playground-gallery]
DB --> GP
SP --> A
M --> Canvas
Smoke[smoke-test.html] --> U
Smoke --> S
Smoke --> E
Smoke --> A
Smoke --> V
Smoke --> G
```
## Entry Points
### `index.html`
The real application shell.
- loads `src/styles/design-system.css`
- renders the entrance curtain, header, panels, canvas, action bar, and toast container
- loads 10 classic `<script>` files in a strict dependency order
- has no framework, bundler, or module loader
Script order is the runtime contract:
1. `src/utils/utils.js`
2. `src/utils/state.js`
3. `src/utils/events.js`
4. `src/engine/audio-engine.js`
5. `src/engine/visual-engine.js`
6. `src/gallery/gallery.js`
7. `src/panels/sound/sound-panel.js`
8. `src/panels/gallery/gallery-panel.js`
9. `src/modes/mode-manager.js`
10. `src/playground.js`
Because everything is loaded as globals, this order matters. `src/playground.js` assumes the prior globals already exist.
### `src/playground.js`
The orchestration nucleus.
Responsibilities:
- entrance particle animation
- enter transition
- engine construction and initialization
- canvas sizing
- gallery boot
- sound panel boot
- ambient particle loop
- mode registration
- save/download/clear/fullscreen button wiring
- panel toggle wiring
- keyboard shortcut wiring
If you want to know what the product actually does today, this is the file.
### `smoke-test.html`
The only real automated harness shipped in the target repo.
- dynamically loads a subset of source files
- performs 18 browser assertions around utils/state/events/audio/visual/gallery
- writes green/red lines into the DOM
- currently has a broken summary counter
### Engine modules
- `src/engine/audio-engine.js`
- Web Audio wrapper for notes, chords, scales, note playback, and chord playback
- `src/engine/visual-engine.js`
- Canvas wrapper for resize, clear, line/circle drawing, seeded palette generation, and placeholder noise
- `src/gallery/gallery.js`
- IndexedDB persistence layer
### Panel / mode modules
- `src/panels/sound/sound-panel.js`
- renders sound controls and quick-play chord UI
- `src/panels/gallery/gallery-panel.js`
- renders gallery thumbnails and empty state
- `src/modes/mode-manager.js`
- registry/switcher for canvas modes
## Data Flow
### App boot flow
1. Browser opens `index.html`.
2. CSS design system establishes the entire visual identity.
3. Utility/state/event globals load.
4. Audio, visual, gallery, panel, and mode globals load.
5. `src/playground.js` runs immediately in an IIFE.
6. The entrance screen appears with animated gold particles.
7. User clicks `Enter` or presses any key.
8. `enterPlayground()`:
- fades the entrance out
- creates and initializes `PlaygroundAudio`
- reveals the playground
- calls `initPlayground()`
- plays a welcome chord
### Main interaction flow
1. `initPlayground()` creates `PlaygroundVisual(canvas)`.
2. Canvas is resized to the container.
3. `PlaygroundGallery` opens IndexedDB and initializes the gallery panel.
4. `SoundPanel.init(audioEngine)` renders the left control surface.
5. `ModeManager.register()` adds two modes:
- `free-draw`
- `ambient`
6. `ModeManager.renderSelector()` creates mode buttons.
7. `ModeManager.switch('ambient')` makes the experience feel alive on load.
### Draw mode flow
1. User switches to `Draw`.
2. `free-draw.init()` binds mouse and touch listeners.
3. Pointer movement draws lines on the canvas via `visualEngine.drawLine()`.
4. X-position is mapped to frequency with `PlaygroundUtils.map()`.
5. `audioEngine.play()` emits short sine notes while drawing.
6. The first interaction hides the “Click anywhere to begin” prompt.
### Save/export flow
1. User clicks `Save`.
2. Canvas is converted to PNG via `canvas.toBlob()`.
3. `FileReader` converts the blob to a data URL.
4. `galleryEngine.save()` writes an object into IndexedDB with:
- `id`
- `created`
- `modified`
- `type`
- `name`
- `data`
- `mimeType`
- `thumbnail`
- `metadata.mode`
5. `gallery:item-saved` fires on the event bus.
6. `GalleryPanel` rerenders.
### Gallery render flow
1. `GalleryPanel.render()` calls `gallery.getAll()`.
2. Results are sorted newest-first by ISO timestamp.
3. Gallery HTML is rebuilt via `innerHTML`.
4. Clicking a thumb currently only shows a toast with the item id prefix.
- there is no real open/view/edit flow yet
### Download flow
1. User clicks `Download`.
2. Canvas blob is created.
3. `PlaygroundUtils.downloadBlob()` synthesizes an `<a download>` link.
4. Browser downloads a PNG snapshot.
## Key Abstractions
### `PlaygroundUtils`
A tiny global helpers object.
Important methods:
- `uuid()` -> `crypto.randomUUID()`
- `clamp()`
- `lerp()`
- `map()`
- `toast()`
- `downloadBlob()`
It is intentionally small, but it is depended on by multiple subsystems.
### `PlaygroundState`
A global mutable state container with sections for:
- `canvas`
- `audio`
- `gallery`
- `ui`
- `recording`
It behaves more like a convenience registry than a true source-of-truth store. Real durable gallery data lives in IndexedDB, not here.
### `PlaygroundEvents`
A minimal event bus:
- `on(event, fn)`
- `emit(event, data)`
- `off(event, fn)`
This is the main loose-coupling seam across modules.
### `PlaygroundAudio`
A lightweight music engine over `AudioContext`.
Capabilities:
- note-name to frequency conversion
- chord construction
- scale construction
- one-shot oscillator playback
- chord playback
- analyser wiring for future visualization/reactivity
### `PlaygroundVisual`
A minimal canvas wrapper.
Capabilities:
- resize canvas and bind context into `PlaygroundState`
- clear canvas
- draw lines and circles
- deterministic palette generation from a seed
- placeholder pseudo-noise function (`perlin2d`, not real Perlin)
### `PlaygroundGallery`
A thin IndexedDB repository.
Contract:
- DB name: `playground-gallery`
- store: `items`
- indexes: `type`, `collection`, `created`
- CRUD methods:
- `init()`
- `save(item)`
- `getById(id)`
- `getAll()`
- `deleteItem(id)`
### `ModeManager`
A registry + switcher for canvas modes.
It holds:
- `modes`
- `current`
- `register()`
- `switch()`
- `renderSelector()`
This is the intended extension point for future experiences.
### `SoundPanel` and `GalleryPanel`
These are rendering adapters that convert state/engine methods into DOM UI.
They keep the app readable by not putting every DOM template inside `src/playground.js`.
## API Surface
This repo has no network API. Its API surface is an in-browser global surface.
### Browser globals exposed by load order
- `PlaygroundUtils`
- `PlaygroundState`
- `PlaygroundEvents`
- `PlaygroundAudio`
- `PlaygroundVisual`
- `PlaygroundGallery`
- `SoundPanel`
- `GalleryPanel`
- `ModeManager`
### Event bus contract
Observed event names:
- `audio:note-played`
- `audio:chord-played`
- `gallery:item-saved`
- `gallery:item-deleted`
- `canvas:mode-changed`
- `playground:ready`
### IndexedDB object contract
Saved gallery items can contain:
- `id`
- `created`
- `modified`
- `type`
- `name`
- `data`
- `mimeType`
- `thumbnail`
- `metadata`
### UI control contract
Important DOM ids and commands:
- `btn-save`
- `btn-download`
- `btn-clear`
- `btn-fullscreen`
- `mode-selector`
- `sound-content`
- `gallery-content`
- `playground-canvas`
Keyboard shortcuts implemented today:
- `Ctrl+S` -> Save
- `Ctrl+D` -> Download
- `F11` -> Fullscreen
- `Escape` -> exit fullscreen
## Test Coverage Gaps
### Current state
What I verified on a fresh clone of `main`:
- `find src -name '*.js' -print0 | xargs -0 -n1 node --check` -> passes
- `python3 -m pytest -q` -> `no tests ran in 0.02s`
- `smoke-test.html` runs 18 browser assertions successfully
- but `smoke-test.html` reports `0 passed, 0 failed` in the summary even while showing 18 green checks
This means the repo has a manual browser smoke harness, but no real automated CI-grade test suite.
### What is covered by `smoke-test.html`
- UUID/clamp/lerp helpers
- default state and snapshot
- event bus firing
- AudioContext construction and music theory helpers
- canvas visual primitives and deterministic palette generation
- IndexedDB save/getAll/getById/delete flow
### What is not covered and should be
1. `src/playground.js` orchestration
- entrance flow
- mode registration
- action bar wiring
- keyboard shortcuts
- panel toggles
2. `ModeManager`
- teardown/init switching order
- active button state
- event emission correctness
3. `SoundPanel`
- BPM slider updates state
- quality button activation
- chord button actually invokes audio engine
- volume slider is rendered but currently unwired
4. `GalleryPanel`
- empty/non-empty rendering
- item-count text updates
- click behavior
- escaping/sanitization of item fields before `innerHTML`
5. cross-module browser integration
- draw mode pointer lifecycle
- touch behavior
- fullscreen and download wiring
- prompt fade-out on first interaction
### Generated missing tests for critical paths
#### A. Mode switching contract test
A Node+VM or browser test should verify teardown/init ordering and active button state.
```python
# pseudo-test idea
# load utils/state/events/mode-manager
# register two fake modes with counters
# switch twice
# assert first teardown ran before second init
# assert PlaygroundState.canvas.mode updated
```
#### B. Smoke summary correctness test
The current smoke harness is lying about pass/fail totals.
```python
# browser-level assertion
# after smoke-test.html finishes,
# count the green result rows and compare them to the h2 summary
```
#### C. GalleryPanel XSS regression test
`GalleryPanel.render()` builds markup with `innerHTML` from gallery item data.
That should be locked down with a test before the panel grows more capable.
```python
# save item with name containing HTML-like content
# render gallery
# assert rendered text is escaped / inert
# assert no unexpected nodes/scripts are created
```
## Security Considerations
### Strengths
- zero network/API attack surface in the app itself
- no dependency tree or third-party script loaders
- local-first persistence using IndexedDB instead of remote storage
- deterministic, transparent runtime based on classic script tags
- reduced-motion CSS support already present
### Risks and caveats
1. `innerHTML` is used in multiple modules.
- `ModeManager.renderSelector()` builds buttons with `innerHTML`
- `SoundPanel.render()` builds control markup with `innerHTML`
- `GalleryPanel.render()` builds gallery thumbnails with `innerHTML`
- The first two are fed by trusted in-repo data.
- `GalleryPanel.render()` is the risky one because it interpolates gallery item data (`item.name`, `item.thumbnail`) coming back from IndexedDB.
2. Browser capability assumptions are strong.
- `crypto.randomUUID()`
- `AudioContext`
- `indexedDB`
- `canvas.toBlob()`
- Fullscreen API
- FileReader
- all are required for the best path
3. No storage limits or cleanup policy.
- IndexedDB can grow without quotas or cleanup UX inside the app
- saved images are stored as data URLs, which can become heavy over time
4. No CSP/integrity story because the repo assumes direct static hosting or file-open execution.
## Dependencies
### Browser/runtime dependencies
- Canvas 2D API
- Web Audio API / `AudioContext`
- IndexedDB
- Fullscreen API
- Blob / `toBlob`
- FileReader
- `crypto.randomUUID()`
- standard DOM APIs
### Project/tooling dependencies
- none declared
- no `package.json`
- no `requirements.txt`
- no build tooling
- no CI workflow files on `main`
### Verification tools used during analysis
- `node --check` for JS syntax verification
- browser execution of `smoke-test.html`
- `pytest` baseline probe, which confirmed there is no Python test suite in this target repo
## Deployment
The deployment model is intentionally trivial.
How to run it today:
- open `index.html` in a browser
- or serve the repo as static files from any plain web server
There is no backend, no API contract, no environment variables, and no deployment automation in the target repo.
Practical verification flow:
1. `find src -name '*.js' -print0 | xargs -0 -n1 node --check`
2. open `smoke-test.html`
3. open `index.html`
4. click `Enter`
5. verify:
- entrance transition
- ambient mode active by default
- sound panel playable
- save creates a gallery item in IndexedDB
- download exports a PNG
## Technical Debt
### Highest-priority debt
1. README vision vs code reality gap
- the README describes a much larger platform than the current implementation
- mainline code today is a polished shell plus two real modes
2. No real automated test suite
- `python3 -m pytest -q` returns `no tests ran`
- the only harness is `smoke-test.html`
- the smoke harness summary is already broken
3. `GalleryPanel.render()` trusts item data too much
- direct `innerHTML` interpolation of stored item fields is a future XSS footgun
4. Global load-order coupling
- every major module assumes previous globals are already loaded
- there is no module isolation or dependency enforcement beyond script order
5. Volume slider is fake right now
- `vol-slider` exists in `SoundPanel.render()`
- there is no listener wiring it to `audioEngine.masterGain`
### Meaningful product debt
- gallery items do not really open; click only toasts an id prefix
- no import/restore/export package flows
- no video forge
- no games floor
- no persistence integration between `PlaygroundState.gallery` and IndexedDB
- `mode-label` in the footer exists but is never updated
- `canvas-overlay` exists but is unused
- `perlin2d()` is explicitly a placeholder, not real Perlin noise
- skip-link CSS exists, but no skip link appears in `index.html`
## Bottom Line
The Playground is a clean sovereign-web prototype: one HTML shell, one design system, a handful of browser engines, and a strong aesthetic identity. It already proves the interaction model.
What it does not yet have is the verification, hardening, and feature depth implied by its own vision. The core challenge now is not invention. It is contraction into truth:
- make the shipped surface match the promise
- turn `smoke-test.html` into real automated coverage
- harden `innerHTML` paths
- finish the panel/mode/gallery interactions that are still only half-born