import fs from 'node:fs' const read=file=>fs.readFileSync(file,'utf8') const expect=(condition,message)=>{if(!condition){console.error(`FAIL: ${message}`);process.exit(1)}console.log(`PASS: ${message}`)} const dialog=read('packages/kbx-ui/src/components/KbxDialog.vue') const drawer=read('packages/kbx-ui/src/components/KbxDrawer.vue') for(const [name,source] of [['Dialog',dialog],['Drawer',drawer]]){ expect(source.includes('restoreTarget')&&source.includes("@hide=\"restoreFocus\"")&&source.includes('target.isConnected'),`${name} restores focus only to a still-valid invoking control after overlay close`) expect(source.includes("target.focus({preventScroll:true})"),`${name} restores focus without unexpected viewport jumps`) } expect(dialog.includes('restoreFocus?:boolean')&&dialog.includes("!props.restoreFocus"),'Dialog supports an explicit owner-managed focus lifecycle escape hatch') const lookupDialog=read('packages/kbx-ui/src/components/KbxLookupDialog.vue') expect(lookupDialog.includes(':restore-focus="false"'),'Lookup disables generic Dialog restore because successful selection intentionally advances to the next field') const excel=read('packages/kbx-ui/src/components/KbxExcelImport.vue') expect(excel.includes('watch(stage, async (current, previous)')&&excel.includes('data-kbx-stage-heading')&&excel.includes("heading?.focus({ preventScroll: true })"),'T08 transfers keyboard focus to the new six-stage work surface after DOM replacement') expect((excel.match(/data-kbx-stage-heading/g)??[]).length>=6,'T08 gives every import stage an explicit focus landing') const operationsApi=read('apps/web/src/modules/common/operations/operationsApi.ts') expect(operationsApi.includes('ClaimWorkItemsResponse')&&operationsApi.includes('claimedCount')&&operationsApi.includes('skippedCount'),'T06 FE consumes the authoritative bulk claim result instead of treating HTTP 200 as all-success') const operations=read('apps/web/src/modules/common/operations/OperationsQueuePage.vue') expect((operations.includes('bulkReceipt')||operations.includes('actionReceipt'))&&(operations.includes('data-kbx-surface="bulk-receipt"')||operations.includes('data-kbx-surface="authoritative-action-receipt"'))&&operations.includes('담당 지정이 일부 처리되었습니다.'),'T06 renders partial bulk outcome as a persistent inline receipt') expect(operations.includes('내 업무 보기')&&operations.includes('최신 상태 조회'),'T06 partial bulk receipt exposes concrete recovery actions') const retryQueue=read('apps/web/src/modules/wms/picking/wmsRetryQueue.ts') expect(retryQueue.includes("STORAGE_PREFIX = 'kbx:wms:safe-retry:v2:'")&&retryQueue.includes('scopeHash(scope')&&retryQueue.includes('scope&&scope!==ANONYMOUS_SCOPE?localStorage:sessionStorage'),'T09 safe-retry storage is tenant/user-scope partitioned and anonymous contexts remain session-only') expect(retryQueue.includes("localStorage.removeItem(LEGACY_STORAGE_KEY)")&&retryQueue.includes('Never replay it'),'T09 retires the old unscoped shared-PDA retry queue instead of replaying it') expect(retryQueue.includes('MAX_QUEUE_ITEMS = 32')&&retryQueue.includes('.filter(validItem)'),'T09 bounds and validates browser-persisted retry commands before replay') const pickingVm=read('apps/web/src/modules/wms/picking/useWmsPicking.ts') expect(pickingVm.includes('KbxScreenPreferenceScopeKey')&&pickingVm.includes('retryScope.value'),'T09 binds retry persistence to the same tenant/user UI scope used by the application shell') const app=read('apps/web/src/App.vue') expect(/app-version="v\d+-fe-[^"]+"/.test(app) && !app.includes('v51-fe-presentation-workbench'),'Runtime feedback/telemetry metadata carries a current versioned FE build identity instead of stale v51') const demo=read('apps/web/src/http/demo/installFrontendDemoApi.ts') expect(demo.includes('claimedCount')&&demo.includes('skippedCount'),'Demo API mirrors the real partial bulk claim response contract') expect(demo.includes("targetField:'orderNo'")&&demo.includes("targetField:'warehouseCode'")&&demo.includes("targetField:'orderQty'"),'Demo T08 supplies a complete required-field mapping so browser Golden Flow can actually reach validation') const referenceHtml=read('apps/web/fe-reference/index.html') const referenceJs=read('apps/web/fe-reference/kbx-fe.js') const referenceCss=read('apps/web/fe-reference/kbx-fe.css') expect(/KBX FE Reference Lab v\d+ ·/.test(referenceHtml) && !referenceHtml.includes('KBX FE Reference Lab v51'),'Static HTML reference carries a current versioned FE build identity') expect(referenceJs.includes('importFocusStep')&&referenceJs.includes('data-import-stage-heading')&&referenceJs.includes('target?.focus({preventScroll:true})'),'Static HTML/JavaScript T08 mirrors stage-focus continuity without stealing focus on progress polling rerenders') expect(!referenceJs.includes("row.actual=row.expected")&&referenceJs.includes('원천 OMS/WMS 값은 이 화면에서 직접 덮어쓰지 않습니다.'),'Static T07 no longer fabricates reconciliation success by mutating reference truth client-side') expect(referenceCss.includes('.import-stage-focus:focus-visible')&&referenceCss.includes('@media(forced-colors:active)'),'Static import stage focus remains visible in normal and forced-color themes') const importE2e=read('tests/e2e/oms-order-import.spec.ts') expect(importE2e.includes("Buffer.from('KBX E2E demo workbook')")&&!importE2e.includes('orders-valid-and-invalid.xlsx\'\)'), 'T08 E2E no longer depends on a missing binary workbook fixture') expect(importE2e.includes('toBeFocused()')&&importE2e.includes("name:'반영 결과'"),'T08 E2E asserts stage-focus continuity through terminal result') const keyboardE2e=read('tests/e2e/kbx-keyboard-regression.spec.ts') expect(keyboardE2e.includes('lookup Escape returns focus')&&keyboardE2e.includes('await expect(customerCode).toBeFocused()'),'Keyboard regression explicitly covers Lookup Esc focus restoration') const manifest=read('packages/kbx-ui/src/registry/componentManifest.ts') expect(manifest.includes("name: 'KbxExcelImport'")&&manifest.includes("version: '1.3.0'"),'T08 focus lifecycle change is component-versioned') expect(manifest.includes("name: 'KbxDialog'")&&manifest.includes("version:'1.1.0'")&&manifest.includes("name: 'KbxDrawer'")&&manifest.includes("version:'1.1.0'"),'Overlay focus lifecycle changes are component-versioned') console.log('PASS: FE interaction closure hardening v56 contract')