Files
KArtSell.Aegis/docs/Design/kbx-foundation-v60-status-canonical-contract-hardening/scripts/validate-fe-work-surface-v54.mjs
T
kjh2064 3f293d8aa8
deploy / deploy (push) Successful in 1m52s
deploy / notify (push) Successful in 1s
V13-FE-006: consolidate approved UI and contract hardening
2026-08-13 02:41:00 +09:00

76 lines
7.5 KiB
JavaScript

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 home = read('packages/kbx-ui/src/shell/KbxHomePage.vue')
for (const label of ['긴급·예외','미저장','진행 작업','알림']) {
expect(home.includes(`label:'${label}'`), `home operational triage includes ${label} lane`)
}
expect(home.includes('attentionTriageGroups') && home.includes('slice(0,perGroup)'), 'home triage limits each source lane instead of letting one source monopolize the workbench')
expect(home.includes('attentionDisplayedCount') && home.includes('kbx-home__triage-group'), 'home exposes visible triage volume and stable lane anatomy')
expect(home.includes("['ArrowLeft','ArrowRight','Home','End']") && home.includes('focusModuleRail'), 'home module rail retains keyboard-first wayfinding')
const navigator = read('packages/kbx-ui/src/components/KbxRecordNavigator.vue')
expect(navigator.includes("itemLabel:'레코드'") && navigator.includes('current>=total'), 'record navigator exposes record position and hard previous/next boundaries')
expect(navigator.includes("emit('previous')") && navigator.includes("emit('next')"), 'record navigator has explicit previous/next interaction contract')
expect(navigator.includes('button:focus-visible') && navigator.includes('@media(forced-colors:active)'), 'record navigator preserves keyboard focus and high-contrast feedback')
const itemMaster = read('apps/web/src/modules/erp/items/ItemMasterPage.vue')
expect(itemMaster.includes('KbxRecordNavigator') && itemMaster.includes('currentRowPosition'), 'T02 item master exposes current-record wayfinding beside the detail work surface')
expect(itemMaster.includes(':previous-disabled="dirty') && itemMaster.includes(':next-disabled="dirty'), 'T02 record navigation is fail-closed while the detail has unsaved changes')
expect(itemMaster.includes('function navigateRecord') && itemMaster.includes('requestChoose([target])'), 'T02 record navigation reuses the existing dirty-safe selection path instead of bypassing it')
const workflow = read('packages/kbx-ui/src/components/KbxWorkflowBar.vue')
expect(workflow.includes('다음 작업') && workflow.includes('nextActionLabel'), 'workflow bar explicitly tells the user what can happen next')
expect(workflow.includes("aria-current=\"state.value===current?'step':undefined\"") && workflow.includes('progressLabel'), 'workflow bar exposes current step semantics and progress')
expect(workflow.includes('현재 상태에서 실행 가능한 다음 작업이 없습니다.'), 'workflow bar explains terminal/blocked states instead of showing an unexplained blank action area')
const fast = read('packages/kbx-ui/src/components/KbxFastEntryPage.vue')
expect(fast.includes('gridTitle?:string') && fast.includes('rowCount?:number') && fast.includes('rowCountUnit?:string'), 'T04 owns fast-entry grid title and row-count anatomy')
expect(fast.includes('slot name="grid-actions"') && fast.includes('KbxSectionHeader'), 'T04 reserves standard grid-level actions beside the work-surface heading')
const itemPrice = read('apps/web/src/modules/erp/item-prices/ItemPriceFastEntryPage.vue')
expect(itemPrice.includes('grid-title="품목 단가 입력"') && itemPrice.includes(':row-count="vm.enteredCount.value"'), 'real T04 item-price screen uses the standard work-surface heading/count contract')
expect(itemPrice.includes('<template #grid-actions>') && itemPrice.includes('오류 {{vm.errors.value.length'), 'real T04 exposes error pressure beside the entry surface')
const md = read('packages/kbx-ui/src/components/KbxMasterDetailPage.vue')
for (const slot of ['master-actions','detail-actions','bottom-actions']) {
expect(md.includes(`slot name="${slot}"`), `T05 exposes standard ${slot} location`)
}
expect(md.includes('masterDescription?:string') && md.includes('detailDescription?:string') && md.includes('bottomDescription?:string'), 'T05 gives each pane an explicit purpose instead of relying on layout alone')
expect(md.includes('masterCount?:number') && md.includes('detailCount?:number') && md.includes('bottomCount?:number'), 'T05 exposes per-pane data volume for scanable master/detail context')
const inventory = read('apps/web/src/modules/erp/inventory/InventoryPage.vue')
expect(inventory.includes('master-description=') && inventory.includes(':master-count='), 'real inventory explorer uses master purpose/count contract')
expect(inventory.includes('detail-description=') && inventory.includes(':detail-count='), 'real inventory explorer uses detail purpose/count contract')
expect(inventory.includes('bottom-description=') && inventory.includes(':bottom-count='), 'real inventory explorer uses history purpose/count contract')
const manifest = read('packages/kbx-ui/src/registry/componentManifest.ts')
expect(manifest.includes("name: 'KbxRecordNavigator'") && manifest.includes("version:'1.0.0'"), 'new record navigator is governed in the component manifest')
expect(manifest.includes("name: 'KbxWorkflowBar'") && manifest.includes("version: '1.2.0'"), 'workflow explicit-next-action change is versioned')
expect(manifest.includes("name: 'KbxFastEntryPage'") && manifest.includes("version:'1.10.0'"), 'T04 work-surface contract change is versioned')
expect(manifest.includes("name: 'KbxMasterDetailPage'") && manifest.includes("version: '1.9.0'"), 'T05 pane anatomy change is versioned')
expect(/name: 'KbxHomePage'[^\n]+version:'1\.(?:1[1-9]|[2-9]\d)\.\d+'/.test(manifest), 'home triage/navigation change is versioned at 1.11.0 or later')
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(referenceHtml.includes('FE Reference Lab v54'), 'static HTML reference identifies the v54 work-surface build')
for (const directive of ["connect-src 'self'", "frame-src 'none'", "worker-src 'none'", "object-src 'none'", "frame-ancestors 'none'"]) {
expect(referenceHtml.includes(directive), `static reference CSP preserves ${directive}`)
}
expect(referenceJs.includes('function attentionTriageGroups') && referenceJs.includes('function attentionLane'), 'static JavaScript reference mirrors four-lane home operational triage')
expect(referenceJs.includes("attentionLane==='running'") && referenceJs.includes("attentionLane==='notice'") && !referenceJs.includes("attentionLane==='notice'||button.dataset.attentionLane==='urgent'"), 'mixed urgent overflow no longer routes incorrectly to notifications only')
expect(referenceJs.includes('class="record-navigator"') && referenceJs.includes('requestMasterTarget'), 'static T02 reference mirrors dirty-safe previous/next record navigation')
expect(referenceJs.includes('class="fast-entry-surface"') && referenceJs.includes('품목 단가 입력'), 'static T04 reference mirrors the named fast-entry work surface')
expect(referenceCss.includes('.attention-lane') && referenceCss.includes('.record-navigator') && referenceCss.includes('.fast-entry-surface'), 'static CSS has concrete triage, record navigation, and fast-entry surfaces')
expect(!referenceCss.includes('var(--focus)') && !referenceCss.includes('var(--surface-hover)'), 'v54 static styles do not rely on undefined theme variables')
expect(referenceCss.includes('@media(forced-colors:active)') && referenceCss.includes('.record-navigator button:focus-visible'), 'static reference preserves high-contrast/focus cues for new v54 interactions')
console.log('PASS: FE work-surface triage and wayfinding hardening v54 contract')