Skip to content

Commit 11f2a52

Browse files
author
DavidQ
committed
docs: add BUILD_PR bundle for minimal Tool Host foundation
1 parent cf51d7b commit 11f2a52

13 files changed

Lines changed: 491 additions & 9 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@ MODEL: GPT-5.4
22
REASONING: high
33

44
COMMAND:
5-
Create BUILD_PR_TOOLS_BOOT_CONTRACT_NORMALIZATION
5+
Create BUILD_PR_TOOL_HOST_FOUNDATION as the next follow-up to tool boot contract normalization.
66

77
Scope:
8-
- normalize tool boot/init contract
9-
- minimal adapters only
10-
- no UI/styling changes
11-
- no editor state refactor
8+
- add a minimal Tool Host foundation
9+
- dynamic load path for normalized tools
10+
- mount/unmount lifecycle handling
11+
- lightweight registry/manifest
12+
- preserve standalone tool pages
13+
- no theme restyling
14+
- no editor-state refactors
15+
- no render-pipeline changes
16+
- do not touch templates/ cleanup in this PR
1217

1318
Validation:
1419
- npm run test:launch-smoke -- --tools
15-
- verify all tools launch
20+
- verify host shell loads selected tool
21+
- verify standalone tool pages still launch
22+
- report exact files changed
1623

1724
Output:
18-
<project>/tmp/BUILD_PR_TOOLS_BOOT_CONTRACT_delta.zip
25+
<project folder>/tmp/BUILD_PR_TOOL_HOST_FOUNDATION_delta.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
docs: add BUILD_PR tools boot contract normalization bundle
1+
docs: add BUILD_PR bundle for minimal Tool Host foundation
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# BUILD_PR_TOOL_HOST_FOUNDATION Report
2+
3+
## Scope Outcome
4+
- Added a minimal Tool Host foundation with dynamic tool loading, one-at-a-time mount/unmount lifecycle handling, and a lightweight manifest built from active tool registry entries.
5+
- Preserved standalone tool pages and existing standalone launch paths.
6+
- Avoided theme restyling, editor-state refactors, and render-pipeline changes.
7+
8+
## Validation
9+
- `npm run test:launch-smoke -- --tools`
10+
- Result: PASS (`9/9` tools)
11+
- Includes standalone launches for active tools and `SpriteEditor_old_keep`
12+
- Includes host page launch: `tools/Tool Host/index.html`
13+
- Host selected-tool load + switch check (CDP):
14+
- Loaded `tools/Tool Host/index.html?tool=asset-browser`
15+
- Verified mounted iframe target: `tools/Asset Browser/index.html`
16+
- Switched selection to `palette-browser`
17+
- Verified remounted iframe target: `tools/Palette Browser/index.html`
18+
- Verified single mounted frame after switch
19+
- Console/runtime errors: none
20+
21+
## Files Changed
22+
- `tools/renderToolsIndex.js`
23+
- `tools/shared/toolHostManifest.js`
24+
- `tools/shared/toolHostRuntime.js`
25+
- `tools/Tool Host/index.html`
26+
- `tools/Tool Host/main.js`
27+
- `docs/dev/reports/launch_smoke_report.md`
28+
- `docs/dev/reports/BUILD_PR_TOOL_HOST_FOUNDATION_report.md`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
VALIDATION CHECKLIST — BUILD_PR_TOOL_HOST_FOUNDATION
2+
3+
Required:
4+
- [ ] host shell loads at least one normalized tool
5+
- [ ] tool switch/unmount path works for target tools in scope
6+
- [ ] standalone tool pages still launch
7+
- [ ] npm run test:launch-smoke -- --tools executed
8+
- [ ] no new console regressions
9+
- [ ] no styling/theme churn beyond host necessities
10+
- [ ] exact files changed reported
11+
12+
Do not accept:
13+
- [ ] hidden editor-state rewrites
14+
- [ ] speculative host abstractions without active-tool evidence

docs/dev/reports/launch_smoke_report.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Launch Smoke Report
22

3-
Generated: 2026-04-11T23:25:17.535Z
3+
Generated: 2026-04-11T23:41:41.835Z
44

55
Filters: games=false, samples=false, tools=true, sampleRange=all
66

@@ -12,5 +12,6 @@ Filters: games=false, samples=false, tools=true, sampleRange=all
1212
| PASS | tool | Sprite Editor | tools\Sprite Editor\index.html | | npm install --prefix ./tmp ws → npm run test:launch-smoke |
1313
| PASS | tool | SpriteEditor_old_keep | tools\SpriteEditor_old_keep\index.html | | npm install --prefix ./tmp ws → npm run test:launch-smoke |
1414
| PASS | tool | Tilemap Studio | tools\Tilemap Studio\index.html | | npm install --prefix ./tmp ws → npm run test:launch-smoke |
15+
| PASS | tool | Tool Host | tools\Tool Host\index.html | | npm install --prefix ./tmp ws → npm run test:launch-smoke |
1516
| PASS | tool | Vector Asset Studio | tools\Vector Asset Studio\index.html | | npm install --prefix ./tmp ws → npm run test:launch-smoke |
1617
| PASS | tool | Vector Map Editor | tools\Vector Map Editor\index.html | | npm install --prefix ./tmp ws → npm run test:launch-smoke |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
READ SET — TOOL HOST FOUNDATION
2+
3+
Read first:
4+
- active tool entry points
5+
- any existing tool boot/init adapters
6+
- tools/shared/platformShell.js
7+
- tools/shared/projectSystem.js and adjacent registry helpers where relevant
8+
9+
Read only immediate dependencies needed to:
10+
- understand current tool boot path
11+
- introduce a host registry/loader
12+
- preserve standalone launch pages
13+
14+
Do not scan:
15+
- games
16+
- samples
17+
- broad engine runtime unrelated to tool mounting
18+
- templates/ (tracked for later roadmap cleanup only)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
TOOL HOST FOUNDATION TARGETS
2+
3+
Primary targets:
4+
1. shared host shell entry
5+
2. tool registry / manifest
6+
3. mount/unmount lifecycle glue
7+
4. config handoff to normalized tools
8+
9+
Must preserve:
10+
- standalone tool pages
11+
- current tool-specific workflows
12+
- boot contract semantics from prior lane
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# BUILD_PR_TOOL_HOST_FOUNDATION
2+
3+
## Purpose
4+
Introduce a minimal Tool Host foundation that can load boot-contract-normalized tools through a shared host shell without changing tool-specific behavior.
5+
6+
## Why This PR Now
7+
The previous lane normalized tool boot contracts. The next safest step is to add a host foundation that can:
8+
- load a selected tool
9+
- pass `container` and `config`
10+
- call `destroy()` on tool switch/unload
11+
- preserve existing standalone tool entry points
12+
13+
## Scope
14+
- host shell foundation only
15+
- dynamic tool loading path
16+
- tool registry / manifest handshake
17+
- minimal adapters where needed
18+
- no tool-specific workflow changes
19+
20+
## In Scope
21+
- shared host entry
22+
- tool registration / lookup model
23+
- mount/unmount lifecycle handling
24+
- lightweight config handoff
25+
- fallback behavior for tools still transitioning
26+
27+
## Out of Scope
28+
- tool UI redesign
29+
- theme restyling
30+
- editor-state refactors
31+
- render-pipeline changes
32+
- templates/ cleanup
33+
- legacy deletion
34+
- broad repo structure cleanup
35+
36+
## Build Strategy
37+
1. create a minimal host shell entry
38+
2. define a single registry/manifest path for active tools
39+
3. mount one tool at a time into a shared container
40+
4. enforce `init(container, config)` and `destroy()` usage where already available
41+
5. preserve direct standalone tool launch pages
42+
43+
## Active Tool Targets
44+
- tools/Asset Browser
45+
- tools/Palette Browser
46+
- tools/Parallax Scene Studio
47+
- tools/Sprite Editor
48+
- tools/Tilemap Studio
49+
- tools/Vector Asset Studio
50+
- tools/Vector Map Editor
51+
52+
## Validation
53+
- npm run test:launch-smoke -- --tools
54+
- direct standalone tool pages still launch
55+
- host shell loads selected tool without console regressions
56+
- tool switch/unmount does not leak prior tool UI into container
57+
58+
## Stop Conditions
59+
Stop and report if:
60+
- any tool requires editor-state redesign to mount
61+
- host work expands into theme restyling
62+
- host work requires render-pipeline rewrites
63+
- registry shape becomes speculative without current tool evidence
64+
65+
## Expected Output
66+
- repo-structured delta ZIP under <project folder>/tmp/
67+
- exact files changed reported
68+
- host foundation kept minimal and reversible

tools/Tool Host/index.html

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Tool Host</title>
7+
<link rel="stylesheet" href="../../src/engine/ui/hubCommon.css" />
8+
<link rel="stylesheet" href="../shared/platformShell.css" />
9+
</head>
10+
<body class="tools-platform-tool-page" data-tools-platform-page="tool">
11+
<div data-tools-platform-header></div>
12+
<div class="wrap app-shell">
13+
<section class="panel">
14+
<h2>Tool Host Foundation</h2>
15+
<p>Load one active tool at a time through a shared host container while keeping direct standalone tool pages unchanged.</p>
16+
<div class="meta">
17+
<label class="field">
18+
Tool
19+
<select data-tool-host-select></select>
20+
</label>
21+
</div>
22+
<div class="meta">
23+
<button type="button" data-tool-host-mount>Load Selected Tool</button>
24+
<button type="button" data-tool-host-unmount>Unmount Tool</button>
25+
<a data-tool-host-standalone href="#" target="_blank" rel="noopener noreferrer">Open Standalone</a>
26+
</div>
27+
<p data-tool-host-status>Tool host idle.</p>
28+
</section>
29+
30+
<section class="panel">
31+
<h3 data-tool-host-current-label>No tool mounted.</h3>
32+
<div data-tool-host-mount-container style="min-height: 980px; border: 1px solid rgba(148, 163, 184, 0.35); border-radius: 10px; overflow: hidden; background: rgba(15, 23, 42, 0.45);"></div>
33+
</section>
34+
</div>
35+
<div data-tools-platform-status></div>
36+
<script type="module" src="../shared/platformShell.js"></script>
37+
<script type="module" src="./main.js"></script>
38+
</body>
39+
</html>

tools/Tool Host/main.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { createToolHostManifest, getToolHostEntryById } from "../shared/toolHostManifest.js";
2+
import { createToolHostRuntime } from "../shared/toolHostRuntime.js";
3+
4+
const refs = {
5+
toolSelect: document.querySelector("[data-tool-host-select]"),
6+
mountButton: document.querySelector("[data-tool-host-mount]"),
7+
unmountButton: document.querySelector("[data-tool-host-unmount]"),
8+
standaloneLink: document.querySelector("[data-tool-host-standalone]"),
9+
statusText: document.querySelector("[data-tool-host-status]"),
10+
currentLabel: document.querySelector("[data-tool-host-current-label]"),
11+
mountContainer: document.querySelector("[data-tool-host-mount-container]")
12+
};
13+
14+
const manifest = createToolHostManifest();
15+
16+
function readSelectedToolId() {
17+
return refs.toolSelect instanceof HTMLSelectElement ? refs.toolSelect.value : "";
18+
}
19+
20+
function writeStatus(text) {
21+
if (refs.statusText instanceof HTMLElement) {
22+
refs.statusText.textContent = text;
23+
}
24+
}
25+
26+
function setCurrentLabel(text) {
27+
if (refs.currentLabel instanceof HTMLElement) {
28+
refs.currentLabel.textContent = text;
29+
}
30+
}
31+
32+
function updateStandaloneHref(toolId) {
33+
if (!(refs.standaloneLink instanceof HTMLAnchorElement)) {
34+
return;
35+
}
36+
const entry = getToolHostEntryById(manifest, toolId);
37+
refs.standaloneLink.href = entry ? entry.launchPath : "#";
38+
}
39+
40+
function writeQueryToolId(toolId, replace = false) {
41+
const url = new URL(window.location.href);
42+
if (toolId) {
43+
url.searchParams.set("tool", toolId);
44+
} else {
45+
url.searchParams.delete("tool");
46+
}
47+
const method = replace ? "replaceState" : "pushState";
48+
window.history[method]({}, "", url.toString());
49+
}
50+
51+
function readInitialToolId() {
52+
const url = new URL(window.location.href);
53+
const fromQuery = url.searchParams.get("tool");
54+
if (fromQuery && getToolHostEntryById(manifest, fromQuery)) {
55+
return fromQuery;
56+
}
57+
return manifest.tools[0]?.id || "";
58+
}
59+
60+
function populateToolSelect(initialToolId) {
61+
if (!(refs.toolSelect instanceof HTMLSelectElement)) {
62+
return;
63+
}
64+
65+
refs.toolSelect.innerHTML = manifest.tools
66+
.map((tool) => `<option value="${tool.id}">${tool.displayName}</option>`)
67+
.join("");
68+
refs.toolSelect.value = getToolHostEntryById(manifest, initialToolId) ? initialToolId : (manifest.tools[0]?.id || "");
69+
}
70+
71+
const runtime = createToolHostRuntime({
72+
manifest,
73+
mountContainer: refs.mountContainer,
74+
onStatus(message) {
75+
writeStatus(message);
76+
},
77+
onMounted(tool) {
78+
setCurrentLabel(`Mounted: ${tool.displayName}`);
79+
},
80+
onUnmounted() {
81+
setCurrentLabel("No tool mounted.");
82+
}
83+
});
84+
85+
function mountSelectedTool(source = "manual") {
86+
const toolId = readSelectedToolId();
87+
if (!toolId) {
88+
writeStatus("Select a tool to mount.");
89+
return;
90+
}
91+
updateStandaloneHref(toolId);
92+
writeQueryToolId(toolId, source === "init");
93+
runtime.mountTool(toolId, {
94+
source,
95+
requestedAt: new Date().toISOString()
96+
});
97+
}
98+
99+
function bindEvents() {
100+
if (refs.mountButton instanceof HTMLButtonElement) {
101+
refs.mountButton.addEventListener("click", () => {
102+
mountSelectedTool("button");
103+
});
104+
}
105+
106+
if (refs.unmountButton instanceof HTMLButtonElement) {
107+
refs.unmountButton.addEventListener("click", () => {
108+
runtime.unmountCurrentTool("manual");
109+
});
110+
}
111+
112+
if (refs.toolSelect instanceof HTMLSelectElement) {
113+
refs.toolSelect.addEventListener("change", () => {
114+
updateStandaloneHref(readSelectedToolId());
115+
mountSelectedTool("select");
116+
});
117+
}
118+
119+
window.addEventListener("popstate", () => {
120+
const toolId = readInitialToolId();
121+
if (refs.toolSelect instanceof HTMLSelectElement) {
122+
refs.toolSelect.value = toolId;
123+
}
124+
updateStandaloneHref(toolId);
125+
mountSelectedTool("popstate");
126+
});
127+
}
128+
129+
function init() {
130+
const initialToolId = readInitialToolId();
131+
populateToolSelect(initialToolId);
132+
updateStandaloneHref(initialToolId);
133+
bindEvents();
134+
mountSelectedTool("init");
135+
}
136+
137+
init();

0 commit comments

Comments
 (0)