+
철근재료표
diff --git a/scripts/verify-cast-detail.cjs b/scripts/verify-cast-detail.cjs
index 100f493..f6c7c0d 100644
--- a/scripts/verify-cast-detail.cjs
+++ b/scripts/verify-cast-detail.cjs
@@ -104,7 +104,9 @@ let ws, send;
await until(`document.getElementById('cip-pile-cageLengthMm')?.value === '12000'`);
check('pile panel shares the column connection', await ev(`document.querySelector('[data-cast-part="pile"]').textContent.includes('공통 연결 방식: 연속')`));
await shot('02-pile-input');
- await click(`document.getElementById('nav-quantity')`);
+ // ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
+ await click(`document.getElementById('nav-rebar')`);
+ await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity clearly marks incomplete design', await ev(`document.getElementById('quantity-body').textContent.includes('전체 수량 미완료') && document.getElementById('quantity-body').textContent.includes('접합부')`));
await shot('03-quantity-incomplete');
await click(`document.getElementById('save-project')`);
diff --git a/scripts/verify-cast-input.cjs b/scripts/verify-cast-input.cjs
index c985778..c0dbd9f 100644
--- a/scripts/verify-cast-input.cjs
+++ b/scripts/verify-cast-input.cjs
@@ -90,7 +90,9 @@ let ws, send;
await until(`document.getElementById('cip-pile-cageLengthMm')?.value === '12000'`);
check('pile panel shares the column connection', await ev(`document.querySelector('[data-cast-part="pile"]').textContent.includes('공통 연결 방식: 연속')`));
await shot('02-pile-input');
- await click(`document.getElementById('nav-quantity')`);
+ // ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
+ await click(`document.getElementById('nav-rebar')`);
+ await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity clearly marks incomplete design', await ev(`document.getElementById('quantity-body').textContent.includes('전체 수량 미완료') && document.getElementById('quantity-body').textContent.includes('접합부')`));
await shot('03-quantity-incomplete');
await click(`document.getElementById('save-project')`);
diff --git a/scripts/verify-cast-rebar.cjs b/scripts/verify-cast-rebar.cjs
index 84ac1eb..f65691d 100644
--- a/scripts/verify-cast-rebar.cjs
+++ b/scripts/verify-cast-rebar.cjs
@@ -81,7 +81,9 @@ const delay=ms=>new Promise(r=>setTimeout(r,ms)); let ws,send;
await send('Emulation.setDeviceMetricsOverride',{width,height:1000,deviceScaleFactor:1,mobile:false});await delay(150);
check(`header ${width} keeps one row and no horizontal overflow`,await ev(`document.querySelector('.top').getBoundingClientRect().height<=65&&document.documentElement.scrollWidth<=innerWidth`));
}
- await click(`document.getElementById('nav-quantity')`);
+ // ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
+ await click(`document.getElementById('nav-rebar')`);
+ await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity contains actual dedicated mains and reports remaining incomplete details',await ev(`document.getElementById('quantity-body').textContent.includes('말뚝-P1')&&document.getElementById('quantity-body').textContent.includes('기둥-P1')&&document.getElementById('quantity-body').textContent.includes('전체 수량 미완료')`));
await shot('04-quantity');
await click(`document.getElementById('nav-rebar')`);await member('기둥');const beforeSave=await ev(table);
diff --git a/scripts/verify-unified-input.cjs b/scripts/verify-unified-input.cjs
index 16065bd..688bb96 100644
--- a/scripts/verify-unified-input.cjs
+++ b/scripts/verify-unified-input.cjs
@@ -137,7 +137,9 @@ let ws, send;
await click(`[...document.querySelectorAll('#rebar-issues li')].find(e=>e.textContent.includes(${JSON.stringify(text)}))?.querySelector('button')`);
check(`${text}: warning opens unified editor`, await ev(`!!document.getElementById('cip-apply') && document.querySelector('#rebar-tabs button[aria-pressed="true"]').textContent.startsWith('기둥·말뚝')`));
}
- await click(`document.getElementById('nav-quantity')`);
+ // ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
+ await click(`document.getElementById('nav-rebar')`);
+ await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity remains explicitly incomplete', await ev(`document.getElementById('quantity-body').textContent.includes('전체 수량 미완료')`));
check('no runtime exceptions', report.errors.length===0);
report.result='PASS';
diff --git a/src/main.ts b/src/main.ts
index a6a16c6..7e46029 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -142,7 +142,7 @@ interface Pier extends PierValues {
}
/** Part toggles in the order of 시안 ① "부재 표시". Each toggle controls one or more scene parts. */
-/** The four stages: ① 형상 생성, ② 형상계획, ③ 철근배근, ④ 재료표 (결과 화면). */
+/** The stages: ① 형상 생성, ② 형상계획, ③ 철근배근. 'quantity' is the 철근재료표 screen opened from ③ (시안: header ①②③, 2026-11-06). */
type Stage = 'shape' | 'plan' | 'rebar' | 'quantity';
/** 부재 표시 toggles (2026-11-03). `pier` parts follow the pier scenes; `abut` names follow the abutment groups (abut-scene.ts). */
@@ -2636,11 +2636,12 @@ function setStage(stage: Stage): void {
placeViewer(stage);
if (rebar3d) rebar3d.group.visible = stage === 'rebar';
// The highlight is the 'current' class (style.css); aria-current is set alongside it for assistive tech.
- const nav: [string, Stage][] = [['nav-shape', 'shape'], ['nav-plan', 'plan'], ['nav-rebar', 'rebar'], ['nav-quantity', 'quantity']];
- for (const [id, key] of nav) {
+ // The 재료표 screen belongs to ③, so ③ stays highlighted while it is open.
+ const nav: [string, Stage[]][] = [['nav-shape', ['shape']], ['nav-plan', ['plan']], ['nav-rebar', ['rebar', 'quantity']]];
+ for (const [id, keys] of nav) {
const btn = $(id);
- btn.classList.toggle('current', stage === key);
- btn.setAttribute('aria-current', String(stage === key));
+ btn.classList.toggle('current', keys.includes(stage));
+ btn.setAttribute('aria-current', String(keys.includes(stage)));
}
renderAll();
}
@@ -4478,11 +4479,11 @@ function renderRebar(): void {
if (focused && state.rebarSelection.size) focused.scrollIntoView({ block: 'center' });
}
-/** ③ keeps only a link to the 철근재료표 screen ④ (승인 2026-10-23). */
+/** ③ keeps a summary line and a link to the 철근재료표 screen (opened from ③; header ①②③, 2026-11-06). */
function renderQuantityLink(box: HTMLElement, rows: RebarRow[], incomplete: readonly string[] = []): void {
const q = buildQuantity(rows, state.material, incomplete);
const line = el('p', `철근재료표 · ${q.missing.length ? '계산된 부재 소계 · 전체 수량 미완료' : '합계'} ${q.kg.toFixed(1)} kg · 할증 포함 ${q.kgWithWaste.toFixed(1)} kg`, 'pnote');
- const link = el('button', '④ 재료표에서 보기', 'btn small');
+ const link = el('button', '철근재료표 보기', 'btn small');
link.type = 'button';
link.addEventListener('click', () => setStage('quantity'));
box.append(line, link);
@@ -4536,7 +4537,6 @@ function renderQuantityScreen(): void {
allButton.textContent = `전체 합계 (${generated}기)`;
allButton.setAttribute('aria-pressed', String(state.quantityAll));
const targets = quantityTargets();
- setHeaderStatus('step4-status', '', targets.length ? (state.quantityAll ? `${targets.length}기` : targets[0].input.name) : '예정');
$('quantity-note').textContent = targets.length
? `${state.quantityAll ? `전체 ${targets.length}기` : targets[0].input.name} · 할증 ${state.material.waste}% (기본 0%, 가정). 삿갓 길이는 h − 100으로 가정(Q-K5). 교각 목록에서 고른 교각만 보며, 전체를 누르면 모든 교각을 보여 줍니다.`
: '계산된 교각이 없습니다. 교각을 생성하세요.';
@@ -5900,7 +5900,7 @@ $('regen').addEventListener('click', regenerateChanged);
$('nav-shape').addEventListener('click', () => setStage('shape'));
$('nav-plan').addEventListener('click', () => setStage('plan'));
$('nav-rebar').addEventListener('click', () => setStage('rebar'));
-$('nav-quantity').addEventListener('click', () => setStage('quantity'));
+$('quantity-back').addEventListener('click', () => setStage('rebar'));
$('mat-fck').addEventListener('change', (ev) => setMaterial('fck', (ev.target as HTMLInputElement).value));
$('mat-waste').addEventListener('change', (ev) => setWaste((ev.target as HTMLInputElement).value));
for (const k of ['small16', 'main25', 'main35', 'main51'] as const) {