diff --git a/docs/CURRENT/AEG-V16-023_SCREEN_STATE_MATRIX_SLICE_NOTE.md b/docs/CURRENT/AEG-V16-023_SCREEN_STATE_MATRIX_SLICE_NOTE.md new file mode 100644 index 00000000..f53353c1 --- /dev/null +++ b/docs/CURRENT/AEG-V16-023_SCREEN_STATE_MATRIX_SLICE_NOTE.md @@ -0,0 +1,17 @@ +# AEG-V16-023 — Screen state matrix regression + +**WBS / Requirement / UI / Test:** AEG-V16-023 / REQ-V16-FEC-07 / UI-V16-FEC-07 / T-V16-FEC-07 + +## Source / Assumption / Unknown / Decision Required + +- **Source:** screen contract, T01–T10 catalogue, and its Vitest suite. +- **Assumption:** each template is exercised by its owning screen integration; this catalogue verifies the shared contract baseline. +- **Unknown:** visual/a11y evidence remains a separate AEG-V16-024 gate. +- **Decision Required:** no screen-specific state policy was invented; missing READY/FORBIDDEN were assigned to the established CRUD template. + +## Evidence + +- Mandatory states are typed as `StandardScreenState`. +- T01 now covers READY and FORBIDDEN; all 13 standard states are covered across T01–T10. +- `pnpm test -- --run src/shared/ui/screen-types/tests/catalogue.spec.ts`: 4/4 passed. +- `pnpm typecheck`: passed. diff --git a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv index ed8e80c1..cc6159ec 100644 --- a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv +++ b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv @@ -13,6 +13,7 @@ AEG-V16-018,S6,Cross,DataContextHeader,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-018 AEG-V16-019,S6,Cross,CommandBar,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-019_COMMAND_BAR_SLICE_NOTE.md; frontend/src/shared/ui/components/KsCommandBar.vue; frontend/src/shared/ui/components/tests/KsCommandBar.spec.ts","FE Lead","2026-08-08: Command boundary now suppresses disabled/busy execute events and exposes aggregate busy state. Actual evidence: targeted Vitest 1/1 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor AEG-V16-018 acceptance and UX/a11y evidence." AEG-V16-020,S6,Cross,CRUD Resource v2,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-020_CRUD_RESOURCE_V2_SLICE_NOTE.md; contracts/ui/crud-resource.v2.json; frontend/src/shared/crud/resourceDefinition.ts; frontend/src/shared/crud/tests/resourceDefinition.spec.ts","FE Lead","2026-08-08: Hardened runtime resource-definition checks for schema versions, permission policy, concurrency/idempotency modes, and sensitive grid columns. Actual evidence: resource definition Vitest 3/3 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor AEG-V16-019 acceptance and UX/a11y evidence." AEG-V16-022,S6,Cross,Optimistic command hook,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-022_OPTIMISTIC_COMMAND_SLICE_NOTE.md; frontend/src/shared/crud/useOptimisticCommand.ts; frontend/src/shared/crud/tests/useOptimisticCommand.spec.ts","FE Lead","2026-08-08: Request creation now freezes one Idempotency-Key per user intent and retries reuse it; 409/412 conflict state remains explicit. Actual evidence: targeted Vitest 2/2 PASS; frontend typecheck PASS. COMPLETED is blocked pending actual CRUD-screen integration and predecessor evidence." +AEG-V16-023,S6,Cross,T01~T10 계약 회귀,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-023_SCREEN_STATE_MATRIX_SLICE_NOTE.md; frontend/src/shared/ui/screen-types/catalogue.ts; frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts","FE Lead","2026-08-08: State contract is typed and all 13 standard states are now covered across T01~T10, including READY and FORBIDDEN. Actual evidence: catalogue Vitest 4/4 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor integration and FE accessibility gate evidence." AEG-X-008,S0,Cross,OpenAPI artifact 고도화,COMPLETED,2026-08-04,.gitea/workflows/openapi-gate.yml + docs/api/openapi.json,BE/FE Architect,"✅ OpenAPI diff gate implemented: CI/CD automation detects breaking changes (3 checks: parameter removal, status code removal, field removal), blocks merge without approval, auto-comments on PR" AEG-VS-00-01,S0,VS-00,정책·범위·실패상태 계약 확정,COMPLETED,2026-08-06,"docs/CURRENT/SLICE_SPECS/VS-00-SLICE_SPEC.md + commit e7913db",PM/Architect,"✅ SLICE_SPEC produced: VS-00-SLICE_SPEC.md (state transitions, RBAC, governance gates, DQ rules, compliance). Commit e7913db. 249/253 tests PASS." AEG-VS-00-02,S0,VS-00,데이터 시점·스키마·정합성 계약,COMPLETED,2026-08-06,"contracts/data/platform-data-contract.v1.json + commit e7913db",Data Architect/DBA,"✅ DATA_CONTRACT v1.0 produced: PIT envelope (published_at/correlation_id/revision), 5 table schemas, DQ rules/lineage, GDPR/PCI-DSS compliance. JSON schema + validation. 249/253 tests PASS." diff --git a/frontend/src/shared/ui/screen-types/catalogue.js b/frontend/src/shared/ui/screen-types/catalogue.js index 29c2363a..c8fc05b7 100644 --- a/frontend/src/shared/ui/screen-types/catalogue.js +++ b/frontend/src/shared/ui/screen-types/catalogue.js @@ -1,5 +1,5 @@ export const screenTemplateCatalogue = Object.freeze([ - { id: 'T01', name: '검색·목록형 CRUD', component: 'SearchListCrudPage', intendedUse: '상품·고객·권한·데이터 Run·추천 검토함', mandatoryStates: ['LOADING', 'EMPTY', 'WARN', 'ERROR', 'UNAUTHORIZED', 'PARTIAL'], mandatoryEvidence: ['filter-url', 'permission', 'export-auth'], antiPatterns: ['client-side-all-data', 'unbounded-export'] }, + { id: 'T01', name: '검색·목록형 CRUD', component: 'SearchListCrudPage', intendedUse: '상품·고객·권한·데이터 Run·추천 검토함', mandatoryStates: ['READY', 'LOADING', 'EMPTY', 'WARN', 'ERROR', 'UNAUTHORIZED', 'FORBIDDEN', 'PARTIAL'], mandatoryEvidence: ['filter-url', 'permission', 'export-auth'], antiPatterns: ['client-side-all-data', 'unbounded-export'] }, { id: 'T02', name: '상세 조회형', component: 'DetailReadPage', intendedUse: '투자제안·상품·포트폴리오·백테스트 결과', mandatoryStates: ['LOADING', 'WARN', 'EXPIRED', 'READONLY', 'UNAUTHORIZED'], mandatoryEvidence: ['as-of', 'version-set', 'audit'], antiPatterns: ['mutable-evidence', 'hidden-version'] }, { id: 'T03', name: '등록·편집 Form', component: 'EditFormPage', intendedUse: '고객·IPS·비용표·권한·설정', mandatoryStates: ['DIRTY', 'CONFLICT', 'PROCESSING', 'ERROR'], mandatoryEvidence: ['zod', 'if-match', 'idempotency-key'], antiPatterns: ['silent-overwrite', 'pinia-form-cache'] }, { id: 'T04', name: 'Master-Detail', component: 'MasterDetailCrudPage', intendedUse: '고객-IPS·추천-항목·Watch-Stage·대사 Run-Break', mandatoryStates: ['LOADING', 'EMPTY', 'DIRTY', 'PARTIAL', 'CONFLICT'], mandatoryEvidence: ['route-selection', 'unsaved-guard', 'version'], antiPatterns: ['selection-only-local', 'detail-n-plus-one'] }, diff --git a/frontend/src/shared/ui/screen-types/catalogue.ts b/frontend/src/shared/ui/screen-types/catalogue.ts index b832aab8..92bd481c 100644 --- a/frontend/src/shared/ui/screen-types/catalogue.ts +++ b/frontend/src/shared/ui/screen-types/catalogue.ts @@ -5,13 +5,13 @@ export interface ScreenTemplateDefinition { name: string component: string intendedUse: string - mandatoryStates: readonly string[] + mandatoryStates: readonly StandardScreenState[] mandatoryEvidence: readonly string[] antiPatterns: readonly string[] } export const screenTemplateCatalogue: readonly ScreenTemplateDefinition[] = Object.freeze([ - { id: 'T01', name: '검색·목록형 CRUD', component: 'SearchListCrudPage', intendedUse: '상품·고객·권한·데이터 Run·추천 검토함', mandatoryStates: ['LOADING','EMPTY','WARN','ERROR','UNAUTHORIZED','PARTIAL'], mandatoryEvidence: ['filter-url','permission','export-auth'], antiPatterns: ['client-side-all-data','unbounded-export'] }, + { id: 'T01', name: '검색·목록형 CRUD', component: 'SearchListCrudPage', intendedUse: '상품·고객·권한·데이터 Run·추천 검토함', mandatoryStates: ['READY','LOADING','EMPTY','WARN','ERROR','UNAUTHORIZED','FORBIDDEN','PARTIAL'], mandatoryEvidence: ['filter-url','permission','export-auth'], antiPatterns: ['client-side-all-data','unbounded-export'] }, { id: 'T02', name: '상세 조회형', component: 'DetailReadPage', intendedUse: '투자제안·상품·포트폴리오·백테스트 결과', mandatoryStates: ['LOADING','WARN','EXPIRED','READONLY','UNAUTHORIZED'], mandatoryEvidence: ['as-of','version-set','audit'], antiPatterns: ['mutable-evidence','hidden-version'] }, { id: 'T03', name: '등록·편집 Form', component: 'EditFormPage', intendedUse: '고객·IPS·비용표·권한·설정', mandatoryStates: ['DIRTY','CONFLICT','PROCESSING','ERROR'], mandatoryEvidence: ['zod','if-match','idempotency-key'], antiPatterns: ['silent-overwrite','pinia-form-cache'] }, { id: 'T04', name: 'Master-Detail', component: 'MasterDetailCrudPage', intendedUse: '고객-IPS·추천-항목·Watch-Stage·대사 Run-Break', mandatoryStates: ['LOADING','EMPTY','DIRTY','PARTIAL','CONFLICT'], mandatoryEvidence: ['route-selection','unsaved-guard','version'], antiPatterns: ['selection-only-local','detail-n-plus-one'] }, @@ -28,3 +28,4 @@ export function getScreenTemplate(id: ScreenTemplateId): ScreenTemplateDefinitio if (!template) throw new Error(`Unknown screen template: ${id}`) return template } +import type { StandardScreenState } from '../contracts/screenContract' diff --git a/frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts b/frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts index 79c26d5a..fca868aa 100644 --- a/frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts +++ b/frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { getScreenTemplate, screenTemplateCatalogue } from '../catalogue' +import type { StandardScreenState } from '../../contracts/screenContract' describe('screen template catalogue', () => { it('defines all ten screen contracts without duplicate IDs or components', () => { @@ -16,6 +17,12 @@ describe('screen template catalogue', () => { } }) + it('covers every standardized screen state across the ten templates', () => { + const allStates: readonly StandardScreenState[] = ['READY','LOADING','EMPTY','WARN','ERROR','UNAUTHORIZED','FORBIDDEN','PARTIAL','DIRTY','CONFLICT','EXPIRED','READONLY','PROCESSING'] + const covered = new Set(screenTemplateCatalogue.flatMap(template => template.mandatoryStates)) + for (const state of allStates) expect(covered).toContain(state) + }) + it('resolves a template by stable ID', () => { expect(getScreenTemplate('T05').component).toBe('ApprovalWorkbenchPage') })