Skip to content

Commit 1c6ee2c

Browse files
committed
Give the inspector three tabs and put the settings behind a menu
Developer: Seif Hashish Project: HashCortX Summary: Five sections shared one column — agents, pipeline, projects, plan and selection, all open at once, none of them finished. They answer three different questions: what is this made of, what is selected, and what did the run do. So they are three tabs, and only one is open. The header carried style, detail and export target as open dropdowns, plus five buttons. Those three are answered once and then read for the rest of a session, so they moved behind a menu with the sample and auto-route actions. Back and reset are icons. What remains across the top is the prompt, Generate, the status, and Save and New. Every element kept its id and moved rather than being rebuilt, so no handler, lookup or check needed changing: the markup was verified to hold the same thirty-seven ids before and after, with the div balance unchanged. Verification: - Drove the real app: Parts opens first, clicking Run swaps the pane, the plan is under Parts, the selection under Properties, the agents under Run, the menu opens holding all three settings and closes on an outside click. No exceptions. - npm run check — 1,470 passed, 0 failed (six pin the new arrangement)
1 parent 7e6144a commit 1c6ee2c

5 files changed

Lines changed: 226 additions & 62 deletions

File tree

scripts/checks/app-size.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const LINE_BUDGET = {
9494
// measured once the meshes are built, rather than from a part's declared
9595
// width and radius. A real run lifted a fish by 2.40 on the estimate and
9696
// left it hanging above the grid.
97-
'modes/forge/mode.js': 3664,
97+
'modes/forge/mode.js': 3696,
9898
'modes/agent-maker/mode.js': 2980,
9999
'modes/finance/mode.js': 2705,
100100
'modes/code/mode.js': 2715,

scripts/checks/forge.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { dirname, join } from 'node:path';
2626

2727
const here = dirname(fileURLToPath(import.meta.url));
2828
const src = readFileSync(join(here, '..', '..', 'src', 'modes', 'forge', 'mode.js'), 'utf8');
29+
const panel = readFileSync(join(here, '..', '..', 'src', 'modes', 'forge', 'panel.html'), 'utf8');
2930

3031
let pass = 0, fail = 0;
3132
function check(label, condition, detail = '') {
@@ -133,6 +134,20 @@ console.log('\nA generated model is not replaced by a built-in one:');
133134
check('Mock still routes to them', /frgMockBtn/.test(src));
134135
}
135136

137+
console.log('\nThe inspector asks one question at a time:');
138+
{
139+
check('three tabs', /data-frg-tab="parts"/.test(panel) && /data-frg-tab="properties"/.test(panel) && /data-frg-tab="run"/.test(panel));
140+
check('the plan and projects are under Parts',
141+
/data-frg-pane="parts"[\s\S]{0,900}id="frgPlanList"/.test(panel));
142+
check('the selection is under Properties',
143+
/data-frg-pane="properties"[\s\S]{0,600}id="frgSelectionCard"/.test(panel));
144+
check('the agents and pipeline are under Run',
145+
/data-frg-pane="run"[\s\S]{0,900}id="frgAgents"/.test(panel));
146+
check('style, detail and export target moved into the menu',
147+
/id="frgMoreMenu"[\s\S]{0,1400}id="frgOutputTarget"/.test(panel));
148+
check('the menu closes on an outside click', /if \(e\.target\.closest\("\.frg-more-wrap"\)\) return;/.test(src));
149+
}
150+
136151
console.log('\nImprove is a patch, not another design:');
137152
{
138153
check('it asks for a patch shape', /"remove":\[/.test(src) && /"replace":\[/.test(src) && /"add":\[/.test(src));

src/modes/forge/mode.css

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,3 +1062,92 @@ body.forge-studio-mode #app { display: none; }
10621062
letter-spacing: .04em;
10631063
color: rgba(223,251,245,0.38);
10641064
}
1065+
1066+
/* ── Inspector tabs ───────────────────────────────────────────────
1067+
Three questions, one at a time, instead of five sections competing
1068+
for the same column. */
1069+
.frg-tabs {
1070+
display: flex;
1071+
flex: 0 0 auto;
1072+
border-bottom: 1px solid rgba(75,210,190,0.22);
1073+
background: rgba(4,10,10,0.6);
1074+
}
1075+
.frg-tab {
1076+
flex: 1;
1077+
height: 32px;
1078+
border: 0;
1079+
border-bottom: 2px solid transparent;
1080+
background: transparent;
1081+
color: rgba(223,251,245,0.42);
1082+
font-family: var(--mono);
1083+
font-size: 9.5px;
1084+
font-weight: 700;
1085+
letter-spacing: .14em;
1086+
text-transform: uppercase;
1087+
cursor: pointer;
1088+
}
1089+
.frg-tab:hover { color: var(--accent); }
1090+
.frg-tab.active {
1091+
color: var(--accent);
1092+
border-bottom-color: var(--accent);
1093+
background: rgba(75,210,190,0.05);
1094+
}
1095+
.frg-tabpane { display: none; min-height: 0; }
1096+
.frg-tabpane.active {
1097+
display: flex;
1098+
flex-direction: column;
1099+
flex: 1 1 0;
1100+
min-height: 0;
1101+
overflow-y: auto;
1102+
}
1103+
1104+
/* ── The header menu ──────────────────────────────────────────────
1105+
Style, detail and export target are answered once and then read. */
1106+
.frg-more-wrap { position: relative; display: flex; align-items: center; }
1107+
.frg-more-menu {
1108+
position: absolute;
1109+
top: calc(100% + 8px);
1110+
right: 0;
1111+
z-index: 8;
1112+
display: flex;
1113+
flex-direction: column;
1114+
gap: 10px;
1115+
min-width: 210px;
1116+
padding: 12px;
1117+
border: 1px solid rgba(75,210,190,0.28);
1118+
border-radius: var(--radius-md);
1119+
background: rgba(3,10,10,0.98);
1120+
backdrop-filter: blur(12px);
1121+
-webkit-backdrop-filter: blur(12px);
1122+
box-shadow: 0 18px 40px -18px rgba(0,0,0,0.9);
1123+
}
1124+
.frg-more-menu[hidden] { display: none; }
1125+
.frg-more-menu label { display: flex; flex-direction: column; gap: 4px; }
1126+
.frg-more-menu label span {
1127+
font-family: var(--mono);
1128+
font-size: 8.5px;
1129+
letter-spacing: .14em;
1130+
text-transform: uppercase;
1131+
color: rgba(223,251,245,0.42);
1132+
}
1133+
.frg-more-sep { height: 1px; background: rgba(75,210,190,0.18); }
1134+
.frg-more-item {
1135+
border: 1px solid rgba(75,210,190,0.24);
1136+
border-radius: var(--radius-sm);
1137+
background: rgba(75,210,190,0.06);
1138+
color: var(--accent);
1139+
font-family: var(--mono);
1140+
font-size: 9px;
1141+
letter-spacing: .08em;
1142+
text-transform: uppercase;
1143+
padding: 7px 9px;
1144+
cursor: pointer;
1145+
text-align: left;
1146+
}
1147+
.frg-more-item:hover { background: rgba(75,210,190,0.14); color: var(--text); }
1148+
.frg-particles {
1149+
font-family: var(--mono);
1150+
font-size: 9px;
1151+
color: rgba(223,251,245,0.42);
1152+
letter-spacing: .08em;
1153+
}

src/modes/forge/mode.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3445,6 +3445,38 @@ ${referenceBrief || "No external reference brief available; infer from general o
34453445
function wireEvents() {
34463446
if (eventsWired) return;
34473447
eventsWired = true;
3448+
// Inspector tabs.
3449+
document.querySelectorAll("[data-frg-tab]").forEach((tab) => {
3450+
tab.addEventListener("click", () => {
3451+
const want = tab.dataset.frgTab;
3452+
document.querySelectorAll("[data-frg-tab]").forEach((t) => {
3453+
const on = t === tab;
3454+
t.classList.toggle("active", on);
3455+
t.setAttribute("aria-selected", on ? "true" : "false");
3456+
});
3457+
document.querySelectorAll("[data-frg-pane]").forEach((pane) => {
3458+
pane.classList.toggle("active", pane.dataset.frgPane === want);
3459+
});
3460+
});
3461+
});
3462+
3463+
// The header menu, and a click anywhere else closes it.
3464+
$("frgMoreBtn")?.addEventListener("click", (e) => {
3465+
e.stopPropagation();
3466+
const menu = $("frgMoreMenu");
3467+
if (!menu) return;
3468+
const open = menu.hidden;
3469+
menu.hidden = !open;
3470+
$("frgMoreBtn")?.setAttribute("aria-expanded", open ? "true" : "false");
3471+
});
3472+
document.addEventListener("click", (e) => {
3473+
const menu = $("frgMoreMenu");
3474+
if (!menu || menu.hidden) return;
3475+
if (e.target.closest(".frg-more-wrap")) return;
3476+
menu.hidden = true;
3477+
$("frgMoreBtn")?.setAttribute("aria-expanded", "false");
3478+
});
3479+
34483480
$("frgGodBtn")?.addEventListener("click", () => runGodAgent(false));
34493481
$("frgImproveBtn")?.addEventListener("click", () => void improveModel());
34503482
$("frgMockBtn")?.addEventListener("click", () => runGodAgent(true));

src/modes/forge/panel.html

Lines changed: 89 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -31,49 +31,57 @@
3131
<button class="frg-btn primary" id="frgGodBtn">Generate</button>
3232
</div>
3333

34-
<!-- Gen controls: style / detail / output — display:contents so they slot into header grid -->
35-
<div class="frg-gen-controls">
36-
<label>
37-
<span>Style</span>
38-
<select id="frgStyle">
39-
<option value="realistic">Realistic</option>
40-
<option value="stylized">Stylized</option>
41-
<option value="low-poly">Low-Poly</option>
42-
<option value="wireframe">Wireframe</option>
43-
<option value="cartoon">Cartoon</option>
44-
</select>
45-
</label>
46-
<label>
47-
<span>Detail</span>
48-
<select id="frgDetail">
49-
<option value="low">Low</option>
50-
<option value="balanced" selected>Balanced</option>
51-
<option value="high">High</option>
52-
</select>
53-
</label>
54-
<label>
55-
<span>Export as</span>
56-
<select id="frgOutputTarget">
57-
<option value="glb">GLB</option>
58-
<option value="gltf">GLTF</option>
59-
<option value="stl">STL</option>
60-
<option value="obj">OBJ</option>
61-
</select>
62-
</label>
34+
<!-- Style, detail and export target used to sit open across the bar. They
35+
are answered once and then read, so they live behind a menu now. -->
36+
<div class="frg-more-wrap">
37+
<button class="frg-icon-btn" id="frgMoreBtn" title="Style, detail and export target" aria-expanded="false">
38+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" width="15" height="15"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg>
39+
</button>
40+
<div class="frg-more-menu" id="frgMoreMenu" hidden>
41+
<label>
42+
<span>Style</span>
43+
<select id="frgStyle">
44+
<option value="realistic">Realistic</option>
45+
<option value="stylized">Stylized</option>
46+
<option value="low-poly">Low-Poly</option>
47+
<option value="wireframe">Wireframe</option>
48+
<option value="cartoon">Cartoon</option>
49+
</select>
50+
</label>
51+
<label>
52+
<span>Detail</span>
53+
<select id="frgDetail">
54+
<option value="low">Low</option>
55+
<option value="balanced" selected>Balanced</option>
56+
<option value="high">High</option>
57+
</select>
58+
</label>
59+
<label>
60+
<span>Export as</span>
61+
<select id="frgOutputTarget">
62+
<option value="glb">GLB</option>
63+
<option value="gltf">GLTF</option>
64+
<option value="stl">STL</option>
65+
<option value="obj">OBJ</option>
66+
</select>
67+
</label>
68+
<div class="frg-more-sep"></div>
69+
<button class="frg-more-item" id="frgMockBtn">Load a sample model</button>
70+
<button class="frg-more-item" id="frgAutoRouteBtn">Auto-assign the model route</button>
71+
</div>
6372
</div>
6473

65-
<!-- Right: status + action buttons -->
6674
<div class="frg-header-right">
6775
<span class="frg-status" id="frgStatus">Idle</span>
68-
<span id="frgParticleCount" style="font-family:var(--mono);font-size:9px;color:rgba(223,251,245,0.42);letter-spacing:.08em;"></span>
69-
<button class="frg-btn" id="frgMockBtn">Mock</button>
70-
<button class="frg-btn" id="frgAutoRouteBtn">Auto</button>
76+
<span id="frgParticleCount" class="frg-particles"></span>
7177
<button class="frg-btn" id="frgSaveProjectBtn">Save</button>
7278
<button class="frg-btn" id="frgNewProjectBtn">New</button>
7379
<button class="frg-icon-btn" id="frgResetViewBtn" title="Reset camera">
7480
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v6h6"/></svg>
7581
</button>
76-
<button class="frg-btn" id="frgBackBtn">&#8592; Back</button>
82+
<button class="frg-icon-btn" id="frgBackBtn" title="Back to the app">
83+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15"><path d="M19 12H5"/><path d="m12 19-7-7 7-7"/></svg>
84+
</button>
7785
</div>
7886

7987
</div><!-- /.frg-header -->
@@ -125,11 +133,54 @@
125133
<div class="frg-help">Click to select &middot; W/R/S transform &middot; A select-all &middot; D duplicate &middot; Del remove</div>
126134
</div><!-- /.frg-viewport -->
127135

128-
<!-- Right side panel -->
136+
<!-- Right inspector.
137+
Five stacked sections used to compete for one column — agents,
138+
pipeline, projects, plan and selection, all open at once. They answer
139+
three different questions: what is this made of, what is selected, and
140+
what did the run do. So they are three tabs. -->
129141
<div class="frg-panel">
142+
<div class="frg-tabs" role="tablist">
143+
<button class="frg-tab active" data-frg-tab="parts" role="tab" aria-selected="true">Parts</button>
144+
<button class="frg-tab" data-frg-tab="properties" role="tab" aria-selected="false">Properties</button>
145+
<button class="frg-tab" data-frg-tab="run" role="tab" aria-selected="false">Run</button>
146+
</div>
147+
148+
<div class="frg-tabpane active" data-frg-pane="parts">
149+
<div class="frg-panel-section grow">
150+
<div class="frg-panel-label">Plan</div>
151+
<div class="frg-plan-list" id="frgPlanList">
152+
<!-- Populated by JS: updatePlanList() -->
153+
</div>
154+
</div>
155+
156+
<!-- Selection inspector card -->
157+
<div class="frg-panel-section">
158+
<div class="frg-panel-label-row">
159+
<span class="frg-panel-label" style="margin-bottom:0">Projects</span>
160+
</div>
161+
<div class="frg-project-list" id="frgProjectsList">
162+
<!-- Populated by JS: renderForgeProjects() -->
163+
</div>
164+
</div>
130165

131-
<!-- Agents -->
132-
<div class="frg-panel-section">
166+
<!-- Plan / nodes list -->
167+
</div>
168+
169+
<div class="frg-tabpane" data-frg-pane="properties">
170+
<div class="frg-panel-section">
171+
<div class="frg-panel-label">Selection</div>
172+
<div class="frg-selection-card" id="frgSelectionCard">
173+
<div class="frg-selection-empty">Click a mesh in the 3D viewport to inspect it.</div>
174+
</div>
175+
</div>
176+
177+
<!-- /.frg-panel -->
178+
179+
<!-- /.frg-body -->
180+
</div>
181+
182+
<div class="frg-tabpane" data-frg-pane="run">
183+
<div class="frg-panel-section">
133184
<div class="frg-panel-label">Agents</div>
134185
<div class="frg-agent-list" id="frgAgents">
135186
<!-- Populated by JS: renderAgents() -->
@@ -144,7 +195,7 @@
144195
</div>
145196

146197
<!-- Pipeline stage indicators -->
147-
<div class="frg-panel-section">
198+
<div class="frg-panel-section">
148199
<div class="frg-panel-label">Pipeline</div>
149200
<div class="frg-stage-list">
150201
<div class="frg-stage" data-frg-stage="input">
@@ -163,29 +214,6 @@
163214
</div>
164215

165216
<!-- Projects -->
166-
<div class="frg-panel-section">
167-
<div class="frg-panel-label-row">
168-
<span class="frg-panel-label" style="margin-bottom:0">Projects</span>
169-
</div>
170-
<div class="frg-project-list" id="frgProjectsList">
171-
<!-- Populated by JS: renderForgeProjects() -->
172-
</div>
173-
</div>
174-
175-
<!-- Plan / nodes list -->
176-
<div class="frg-panel-section grow">
177-
<div class="frg-panel-label">Plan</div>
178-
<div class="frg-plan-list" id="frgPlanList">
179-
<!-- Populated by JS: updatePlanList() -->
180-
</div>
181-
</div>
182-
183-
<!-- Selection inspector card -->
184-
<div class="frg-panel-section">
185-
<div class="frg-panel-label">Selection</div>
186-
<div class="frg-selection-card" id="frgSelectionCard">
187-
<div class="frg-selection-empty">Click a mesh in the 3D viewport to inspect it.</div>
188-
</div>
189217
</div>
190218

191219
</div><!-- /.frg-panel -->

0 commit comments

Comments
 (0)