39 lines
3.8 KiB
JavaScript
39 lines
3.8 KiB
JavaScript
import fs from 'node:fs'
|
|
const read=p=>fs.readFileSync(p,'utf8')
|
|
const failures=[];const check=(ok,msg)=>{if(!ok)failures.push(msg)}
|
|
|
|
const session=read('apps/web/src/shell/workspaceSessionStore.ts')
|
|
for(const token of ['kbx.workspace.session.v1','sessionStorage','dirtyDiscardedCount','tabs.filter(tab => !tab.dirty)','return parsed.pathname','MAX_STORED_BYTES','MAX_TABS'])check(session.includes(token),`workspace resume store missing: ${token}`)
|
|
check(!session.includes('localStorage'),'workspace resume must use sessionStorage, not persistent localStorage')
|
|
check(session.includes('Query/hash may contain transient'),'workspace resume must document query/hash privacy boundary')
|
|
|
|
const frame=read('apps/web/src/shell/KbxAppFrame.vue')
|
|
for(const token of ['loadWorkspaceSession','saveWorkspaceSession','restoreWorkspaceForScope','safeWorkspacePath(item.screenId,item.path)','hasPermission(screen.permissions)','store.restoreTabs(restored)','미저장 편집','persistWorkspaceSession'])check(frame.includes(token),`AppFrame workspace resume hardening missing: ${token}`)
|
|
|
|
const store=read('apps/web/src/shell/workspaceStore.ts')
|
|
for(const token of ['function restoreTabs','resumed:true','function notifyShell','tab.resumed=false'])check(store.includes(token),`workspace store resume lifecycle missing: ${token}`)
|
|
check(/try\{currentRaw=localStorage\.getItem/.test(store),'navigation preference initial localStorage reads must be fail-soft')
|
|
|
|
const contract=read('packages/kbx-contracts/src/navigation.ts')
|
|
check(contract.includes('resumed?: boolean')&&contract.includes('Never implies restored form data'),'workspace tab resume contract must explicitly reject form-data restoration semantics')
|
|
|
|
const tabs=read('packages/kbx-ui/src/shell/KbxWorkspaceTabs.vue')
|
|
for(const token of ['focusableKey','props.activeKey??visibleTabs.value[0]?.key','tab.resumed&&!tab.dirty','이전 세션에서 복원됨',':tabindex="tab.key===focusableKey?0:-1"'])check(tabs.includes(token),`Workspace Tabs resume/a11y contract missing: ${token}`)
|
|
const homeNav=read('packages/kbx-ui/src/shell/homeNavigation.ts')
|
|
check(homeNav.includes("tab.resumed&&!tab.dirty?'복원'"),'Home workbench must distinguish restored session location from ordinary open work')
|
|
|
|
const wms=read('packages/kbx-ui/src/wms/KbxWmsMobilePage.vue')
|
|
for(const token of ["retryLabel?: string","@retry=\"emit('retry')\"","@empty-action=\"emit('emptyAction')\""])check(wms.includes(token),`T09 mobile state recovery missing: ${token}`)
|
|
const pickingVm=read('apps/web/src/modules/wms/picking/useWmsPicking.ts')
|
|
check(pickingVm.includes('const contentState = computed<KbxAsyncState>')&&pickingVm.includes("query.isError.value")&&pickingVm.includes('reload: () => query.refetch()'),'WMS Picking must map TanStack query state to T09 recovery')
|
|
const pickingPage=read('apps/web/src/modules/wms/picking/WmsPickingPage.vue')
|
|
check(pickingPage.includes(':content-state="vm.contentState.value"')&&pickingPage.includes('@retry="vm.reload"'),'WMS Golden Picking must bind common T09 retry loop')
|
|
|
|
const ref=read('apps/web/fe-reference/kbx-fe.js')
|
|
for(const token of ['persistWorkspaceSession','restoreWorkspaceSession','kbx-fe-reference-workspace-v1','dirtyDiscardedCount','resumed:true','focusableId','이전 미저장'])check(ref.includes(token),`FE Reference workspace resume parity missing: ${token}`)
|
|
const refCss=read('apps/web/fe-reference/kbx-fe.css')
|
|
check(refCss.includes('.tab-resumed')&&refCss.includes('color:var(--primary)'),'FE Reference resumed tab must remain semantic-theme visible')
|
|
|
|
if(failures.length){console.error('FE resume/navigation v46 validation failed:');for(const f of failures)console.error(`- ${f}`);process.exit(1)}
|
|
console.log('FE resume/navigation v46 PASS: safe session-only tab resume, dirty exclusion, path privacy, Home resume, roving focus, storage fail-soft.')
|