refactor: 프론트엔드 리펙토링
This commit is contained in:
@@ -247,6 +247,8 @@ domain, application state, query cache, global store, diagnostics에 넣지 않
|
||||
### 3.2 picker baseline과 enhancement
|
||||
|
||||
- 접근 가능한 `<input type="file">`가 모든 browser의 canonical baseline이다.
|
||||
baseline adapter는 `input.click()`으로 이 경로를 활성화한다. `showPicker()`
|
||||
존재 여부는 portable baseline capability의 판정 기준으로 사용하지 않는다.
|
||||
- `showOpenFilePicker()`와 `showSaveFilePicker()`는 runtime method별 feature
|
||||
detection을 거친 progressive enhancement다. UA sniffing을 사용하지 않는다.
|
||||
- picker 호출은 click/keyboard handler의 첫 browser action이어야 한다. 그 전에
|
||||
@@ -987,7 +989,7 @@ recipe의 공통 code는 UI/telemetry에 raw DOMException을 노출하지 않기
|
||||
| `PERMISSION_DENIED` | picker/save permission | baseline/manual fallback |
|
||||
| `LIMIT_EXCEEDED` | count/bytes/page/buffer budget | 입력 축소 |
|
||||
| `POLICY_REJECTED` | type/cache/data policy | 저장·전송 금지 |
|
||||
| `BLOCKED` | IndexedDB older context | 다른 탭 close/retry UI |
|
||||
| `BLOCKED` | IndexedDB older context가 live open/upgrade를 막는 중 | 다른 탭 close/retry UI |
|
||||
| `CONFLICT` | revision/generation/idempotency conflict | authoritative re-read |
|
||||
| `MIGRATION_FAILED` | schema/data migration | read-only/online-only |
|
||||
| `QUOTA_EXCEEDED` | actual write failure | rollback, reconstructable GC, bounded retry |
|
||||
@@ -999,6 +1001,12 @@ recipe의 공통 code는 UI/telemetry에 raw DOMException을 노출하지 않기
|
||||
| `UNAVAILABLE` | browser/worker/storage temporarily unavailable | documented fallback |
|
||||
| `UNSUPPORTED` | capability absence | baseline/online-only |
|
||||
|
||||
`IndexedDbConnectionStatus.BLOCKED`는 대기 중인 live attempt의 observable
|
||||
상태다. blocked deadline이 끝나 `open()`이 `BLOCKED` failure로 settle되면
|
||||
그 attempt는 더 이상 열리는 중이 아니므로 status는
|
||||
`CLOSED / NOT_OPENED`로 전이한다. 늦게 성공한 native connection은 즉시 닫고
|
||||
settled status를 되살리지 않는다.
|
||||
|
||||
user dismissal은 failure가 아니라 outcome이다. browser DOMException name은
|
||||
adapter에서 이 vocabulary로 mapping하고 raw message/stack은 local bounded
|
||||
diagnostic에도 기본 저장하지 않는다.
|
||||
@@ -1085,18 +1093,17 @@ Playwright Chromium, Firefox, WebKit에서 실제 secure-origin API를 검사한
|
||||
`test:browser-capabilities`의 JUnit을
|
||||
`verify:browser-capability-evidence`가 읽어 세 engine의 testcase 집합 동일성,
|
||||
양수 실행 수, zero failure/error/skipped와 skipped/failure node 부재를 강제한다.
|
||||
현재 checkout의 source suite는 engine마다 정확히 같은 14개 case(File 2,
|
||||
IndexedDB 4, OPFS/Cache/StorageManager 각 1, cross-context invalidation 2,
|
||||
presigned streaming download/multipart upload/Image CDN 각 1)를 정의한다.
|
||||
promotion artifact는 Chromium/Firefox/WebKit의 14개씩, 총 42개가 모두
|
||||
실행되어야 한다. 이 host의
|
||||
WebKit은 필수 native libraries(예:
|
||||
`libbacktrace.so.0`, `libevent-2.1.so.7`, `libjxl.so.0.8`,
|
||||
`libavif.so.16`과 WPE 계열) 부재로 실행되지 않았고 현재 보존 artifact도
|
||||
Chromium/Firefox 14개씩 총 28개만 통과한 상태다. 따라서 promotion evidence를
|
||||
충족하지 않으며 verifier가 실패하는 것이 정상이다. `INSTALLED` 전에는 필요한
|
||||
system dependency가 있는 CI/device에서 세 engine 전체 evidence를 새로 생성해야
|
||||
한다.
|
||||
browser capability promotion은 source suite에서 관찰된 동일 testcase set을
|
||||
Chromium/Firefox/WebKit에서 모두 실행하는 규칙으로 관리한다. case 수를 문서
|
||||
상수로 복제하지 않고 `verify:browser-capability-evidence`가 Chromium 결과를
|
||||
baseline set으로 계산해 Firefox/WebKit의 set 동일성, 양수 실행 수,
|
||||
failure/error/skipped 0과 failure/skipped node 부재를 검증한다.
|
||||
|
||||
capability absence도 skip 사유가 아니다. 예를 들어 어떤 engine에서
|
||||
`navigator.storage`가 없다면 동일 testcase 안에서 adapter의
|
||||
`UNSUPPORTED / STORAGE_ESTIMATE / ONLINE_ONLY` 결과를 browser truth로
|
||||
검증한다. 세 engine을 실행할 수 없는 host의 artifact는 promotion evidence로
|
||||
사용하지 않는다.
|
||||
|
||||
- native input keyboard/focus/same-file reselection/multiple/dismissal
|
||||
- Chromium conditional picker/save enhancement와 다른 engine fallback
|
||||
|
||||
@@ -48,18 +48,39 @@ its business requirement.
|
||||
|
||||
## Scenario B — IndexedDB local draft
|
||||
|
||||
An executable consumer probe lives at:
|
||||
The original executable consumer probe remains at:
|
||||
|
||||
`tests/contract/consumer-experience/indexeddb-local-draft.test.ts`
|
||||
|
||||
with the feature-owned fixture:
|
||||
with its isolated fixture:
|
||||
|
||||
`tests/contract/consumer-experience/fixtures/local-draft-feature.ts`
|
||||
|
||||
The fixture models a small local-draft feature with save/find/remove and
|
||||
optimistic revision checking.
|
||||
The architecture is now also exercised by a real vertical slice under:
|
||||
|
||||
Measured result:
|
||||
`src/features/local-draft-feature`
|
||||
|
||||
It owns the Local Draft domain/application API and binds an
|
||||
`IndexedDbRepositoryPort<LocalDraft, never>` in its feature adapter. The
|
||||
cross-capability composition contract lives at:
|
||||
|
||||
`tests/contract/reusable-capability/feature-adapter-composition.test.ts`
|
||||
|
||||
That test composes one HTTP-only contribution and the IndexedDB-only Local Draft
|
||||
contribution through the same generic catalog path.
|
||||
|
||||
The browser-level composition proof lives at:
|
||||
|
||||
`tests/browser-capabilities/local-draft-composition.spec.ts`
|
||||
|
||||
It runs against native browser IndexedDB. The test creates the platform
|
||||
`createIndexedDbRuntime`, exposes that runtime through the typed
|
||||
`createIndexedDbRepositoryProvider`, composes
|
||||
`LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION`, then executes Local Draft
|
||||
save/find/remove through the feature API. The Local Draft feature still does
|
||||
not import the IndexedDB runtime or native browser API.
|
||||
|
||||
Measured probe result:
|
||||
|
||||
| Metric | Result |
|
||||
| --- | ---: |
|
||||
@@ -81,8 +102,11 @@ The contract test rejects feature source that reaches for
|
||||
|
||||
## What this does and does not prove
|
||||
|
||||
The probe confirms that **feature business code does not need native IndexedDB
|
||||
knowledge** once an `IndexedDbRepositoryPort` has been composed.
|
||||
The contract probe confirms that **feature business code does not need native
|
||||
IndexedDB knowledge** once an `IndexedDbRepositoryPort` has been composed.
|
||||
The browser-capability proof additionally confirms that the same feature
|
||||
contribution works when that port is backed by the repository's real
|
||||
`createIndexedDbRuntime` and native IndexedDB implementation.
|
||||
|
||||
It does not prove that composition of `createIndexedDbRuntime` is cheap.
|
||||
That constructor still owns substantial infrastructure policy:
|
||||
@@ -96,10 +120,15 @@ That constructor still owns substantial infrastructure policy:
|
||||
- durability, scheduling and observation.
|
||||
|
||||
That complexity belongs at the composition/platform boundary, not in the
|
||||
feature. A new `create...Repository<T>` convenience factory should be added
|
||||
only when a second real product consumer demonstrates which subset is stable
|
||||
enough to become a reusable profile. Creating one now would guess at policy and
|
||||
risk producing a universal storage abstraction.
|
||||
feature. The Local Draft vertical slice is now the second concrete consumer, and
|
||||
it confirms that the stable seam is the typed repository provider plus a
|
||||
feature-owned repository identity. It does **not** show that dataset scope,
|
||||
retention, migration, codec or lifecycle-authority configuration can be safely
|
||||
collapsed into one universal `create...Repository<T>` factory.
|
||||
|
||||
A convenience profile should therefore be introduced only after another
|
||||
IndexedDB-backed product feature repeats the same infrastructure policy, not
|
||||
merely because two features consume the same repository port.
|
||||
|
||||
## Consumer-quality review checklist
|
||||
|
||||
|
||||
@@ -189,17 +189,18 @@ chunk별 `CapabilityResult<Uint8Array>`를 반환한다. backend upload example
|
||||
실제 upload feature는 이 예시를 그대로 import하지 않고 purpose와 backend
|
||||
protocol에 맞게 contract를 더 좁힌다.
|
||||
|
||||
현재 checkout의 browser source suite는 engine마다 같은 14개 case(File 2,
|
||||
IndexedDB 4, OPFS/Cache/StorageManager 각 1, cross-context invalidation 2,
|
||||
presigned streaming download/multipart upload/Image CDN 각 1)를 정의한다.
|
||||
promotion artifact는 Chromium/Firefox/WebKit 각각 14개, 총 42개를 모두
|
||||
실행해야 한다. WebKit은 현재
|
||||
host의 필수 native libraries(예:
|
||||
`libbacktrace.so.0`, `libevent-2.1.so.7`, `libjxl.so.0.8`,
|
||||
`libavif.so.16`과 WPE 계열) 부재로 실행되지 않았다. 보존 artifact는
|
||||
Chromium/Firefox 14개씩 총 28개만 통과했으므로
|
||||
`verify:browser-capability-evidence`가 실패하는 것이 정상이다. 세 engine
|
||||
evidence가 완성되기 전에는 product 상태를 `INSTALLED`로 올리지 않는다.
|
||||
browser capability promotion은 source suite가 정의한 동일 testcase set을
|
||||
Chromium/Firefox/WebKit에서 모두 실행해야 한다. 구체적인 case 개수는 이 결정
|
||||
문서에 고정하지 않는다. `verify:browser-capability-evidence`가 Chromium
|
||||
artifact에서 baseline set을 계산하고 Firefox/WebKit과의 set 동일성 및
|
||||
failure/error/skipped 0을 기계적으로 검증한다.
|
||||
|
||||
engine별 native capability 차이는 testcase를 skip하는 이유가 아니다. capability가
|
||||
없으면 adapter의 명시적 unsupported/degraded result를 같은 testcase에서
|
||||
검증한다. baseline `<input type="file">` activation은 portable한
|
||||
`input.click()` 경로를 사용하고, `showOpenFilePicker()` 같은 API는 별도
|
||||
progressive enhancement로 유지한다. 세 engine evidence가 완성되기 전에는
|
||||
product 상태를 `INSTALLED`로 올리지 않는다.
|
||||
|
||||
## 선택 이후 필요한 구현
|
||||
|
||||
|
||||
@@ -105,6 +105,39 @@ Central installed catalogs are aggregation points only:
|
||||
Feature-specific composition belongs under the feature itself. Central
|
||||
catalogs must not grow feature-specific branching logic.
|
||||
|
||||
Adapter contributions declare the platform capabilities they consume through
|
||||
`needs`. The generic contribution seam in
|
||||
`src/features/feature-adapter-contribution.ts` derives the context from that
|
||||
list, so an HTTP-only feature cannot reach IndexedDB and an IndexedDB-only
|
||||
feature does not receive the HTTP executor. It also binds
|
||||
`featureId -> ApplicationFeatureInputs[featureId]` at the contribution
|
||||
definition site instead of recovering that relationship with a final catalog
|
||||
cast.
|
||||
|
||||
A contribution becomes composable only through
|
||||
`defineFeatureAdapterContribution()`. That factory adds the private
|
||||
contribution brand required by `composeFeatureAdapterInputs()`; a raw object
|
||||
with the same visible fields is rejected by TypeScript and checked again at
|
||||
runtime. The negative type fixture
|
||||
`invalid-direct-feature-composition.ts` is part of FE-GATE-003 so this
|
||||
authority cannot be bypassed by calling the composer directly.
|
||||
|
||||
For IndexedDB-backed features, `createIndexedDbRepositoryProvider()` is the
|
||||
composition helper that maps feature-owned repository identities to typed
|
||||
`IndexedDbRepositoryPort` instances. It does not create a universal storage
|
||||
repository or move dataset/migration/lifecycle policy into the feature.
|
||||
|
||||
The repository now has two executable contribution shapes:
|
||||
|
||||
- Reference feature: `needs: ["http"]`
|
||||
- Local Draft feature: `needs: ["indexedDb"]`
|
||||
|
||||
Local Draft is a compiled architecture consumer used to prove the second
|
||||
capability shape; it is not added to the default product manifest. Its
|
||||
browser-capability test composes the feature over the real
|
||||
`createIndexedDbRuntime` and native IndexedDB, proving that this seam is not
|
||||
limited to an in-memory contract fixture.
|
||||
|
||||
## Presentation consumer surface
|
||||
|
||||
`ApplicationProvider` remains the composition root for presentation, but new
|
||||
|
||||
@@ -337,7 +337,14 @@ Run on the landed tree. Only what actually passed is claimed as passing.
|
||||
| --- | --- | --- |
|
||||
| `tests/unit/ci-workflow-generation.test.ts` | 82 failed / 325 passed | Identical on the pre-change baseline (`git stash` comparison). The subprocess gates it spawns cannot run in this sandbox. |
|
||||
| `tests/unit/ci-artifact-contract.test.ts` | fails | Unchanged pre-existing sandbox, cgroup and `/tmp` permission behaviour. |
|
||||
| `tests/unit/security-followup.test.ts`, `tests/unit/provider-guardian-transaction.test.ts`, `tests/unit/risk-coverage.test.ts` | flaky under full-suite load | All three pass in a fresh process (78 passed together). They spawn and reap process groups, so their timing assertions are load sensitive. |
|
||||
| `tests/unit/security-followup.test.ts`, `tests/unit/provider-guardian-transaction.test.ts`, `tests/unit/risk-coverage.test.ts` | flaky under full-suite load | Historical baseline result. All three passed in a fresh process (78 passed together). |
|
||||
|
||||
> Current classification update (2026-09-18): the provider guardian transaction
|
||||
> suite moved to `tests/integration/provider-guardian-transaction.test.ts`.
|
||||
> It spawns child processes and exercises filesystem/IPC/process lifecycle, so it
|
||||
> is not part of the pure unit pool. Its READY/PUBLISHED wait uses a dedicated
|
||||
> test watchdog rather than treating a 1-second scheduler delay as a production
|
||||
> protocol deadline.
|
||||
|
||||
### Destructive fixture hazard — fixed
|
||||
|
||||
@@ -375,7 +382,7 @@ files above plus the two flaky-under-load ones:
|
||||
| `tests/unit/ci-workflow-generation.test.ts` | 82 | Identical on the pre-change baseline (`git stash` comparison). Its subprocess gates cannot run in this sandbox. |
|
||||
| `tests/unit/ci-artifact-contract.test.ts` | 19 | Unchanged pre-existing sandbox, cgroup and `/tmp` permission behaviour. |
|
||||
| `tests/unit/security-followup.test.ts` | 2 | Passes in isolation. |
|
||||
| `tests/unit/provider-guardian-transaction.test.ts` | 1 | Passes in isolation. |
|
||||
| `tests/unit/provider-guardian-transaction.test.ts` (historical path; now `tests/integration/provider-guardian-transaction.test.ts`) | 1 | Historical baseline: passed in isolation; current suite is classified as process integration. |
|
||||
|
||||
1619 passed / 1723 total, and `tests/unit/removal-fixture.test.ts`,
|
||||
`tests/unit/supply-chain.test.ts` and
|
||||
|
||||
@@ -148,6 +148,9 @@ read-only나 online-only가 사용자 작성 내용을 잃게 한다면 먼저 e
|
||||
### 신호
|
||||
|
||||
- `BLOCKED`, `UPGRADE_BLOCKED` 또는 blocked duration bucket 증가
|
||||
- live attempt가 기다리는 동안 status는 `BLOCKED`
|
||||
- blocked deadline settle 뒤에는 open result가 `BLOCKED` failure여도
|
||||
runtime status는 `CLOSED / NOT_OPENED`
|
||||
- `versionchange` 뒤 connection이 남음
|
||||
- repeated reload/update loop
|
||||
- open/maintenance의 `POLICY_REJECTED`: immutable dataset binding missing/mismatch
|
||||
@@ -500,16 +503,13 @@ browser engine/version, fixture ID, fault phase, bounded counts/buckets와 PASS/
|
||||
|
||||
promotion 직전에는 다음 repository evidence도 함께 보존한다.
|
||||
|
||||
- `test:browser-capabilities`가 만든 JUnit에서 Chromium, Firefox, WebKit이 동일
|
||||
14개 testcase set(File 2, IndexedDB 4, OPFS/Cache/StorageManager 각 1,
|
||||
cross-context invalidation 2, presigned streaming download/multipart
|
||||
upload/Image CDN 각 1)을 실제 실행해 총 42개이며 failure/error/skipped가 모두
|
||||
0이어야 한다.
|
||||
`verify:browser-capability-evidence`가 engine 집합과 testcase 동일성을
|
||||
기계적으로 검증한다. 현재 artifact는 Chromium/Firefox 14개씩 총 28개가
|
||||
통과했지만 WebKit 실행에 필요한 native libraries(예:
|
||||
`libbacktrace.so.0`, `libevent-2.1.so.7`, `libjxl.so.0.8`,
|
||||
`libavif.so.16`과 WPE 계열)가 이 host에 없으므로 아직 promotion 가능 상태가
|
||||
- `test:browser-capabilities`가 만든 JUnit에서 Chromium, Firefox, WebKit이
|
||||
source suite의 동일 testcase set을 실제 실행하고 failure/error/skipped가 모두
|
||||
0이어야 한다. 구체적인 case 수는 이 runbook에 복제하지 않는다.
|
||||
`verify:browser-capability-evidence`가 Chromium을 baseline으로 testcase set과
|
||||
engine 집합을 동적으로 검증한다. native capability가 없는 engine도 testcase를
|
||||
skip하지 않고 adapter의 명시적 `UNSUPPORTED`/degraded 결과를 검증해야 한다.
|
||||
세 engine 중 하나라도 실행되지 않은 artifact는 promotion 가능 상태가
|
||||
아니다.
|
||||
- `artifacts/quality/vite-module-inventory.json`에서 optional runtime source root가
|
||||
production chunk에 없음을 `check:optional-recipes`로 검증한다.
|
||||
|
||||
@@ -105,6 +105,24 @@ component suite로 분리되어 있다.
|
||||
실제 QueryClient 위에서 검증한다. HTTP 자동 retry가 소유자이므로 이 adapter의
|
||||
query/mutation vendor retry는 꺼져 있다.
|
||||
|
||||
같은 기준을 대형 unit suite에도 적용한다. 줄 수를 기준으로 자르지 않고 실패가
|
||||
가리켜야 하는 behavior owner를 기준으로 분리한다.
|
||||
|
||||
- `public-response-cache.test.ts`: 일반 stage/activate/lookup/cache contract
|
||||
- `public-response-cache-repair.test.ts`: active release repair의 failure atomicity
|
||||
- `resumable-upload-runtime.test.ts`: upload/reconcile/control-plane 흐름
|
||||
- `resumable-upload-runtime-teardown.test.ts`: bounded drain/raw provider teardown
|
||||
- `security-followup.test.ts`: archived local evidence
|
||||
- `security-provider-evidence.test.ts`: provider signature/supervision/process lifecycle
|
||||
- `security-promotion-staging.test.ts`: private staging seal/replay/CLI identity
|
||||
- `tests/integration/provider-guardian-transaction.test.ts`: child process,
|
||||
filesystem, IPC frame, READY/PUBLISHED handshake와 process lifecycle. pure unit
|
||||
pool에 두지 않으며 handshake budget은 protocol timeout이 아닌 test watchdog이다.
|
||||
- 각 `*-fixture.ts`: 해당 owner들 사이에서만 공유하는 deterministic test fixture
|
||||
|
||||
이 분리는 production owner와 test failure surface를 맞추기 위한 것이며, 단순
|
||||
LOC 감축 목적이 아니다.
|
||||
|
||||
#### Form과 route 위험
|
||||
|
||||
form component/reference feature test가 error summary, 첫 오류 focus, Zod
|
||||
|
||||
@@ -11,7 +11,7 @@ Each gate is blocking in its declared scope. Failures are not downgraded with
|
||||
| unit | `pnpm test:unit` | Node-only domain/application/pure policy/runtime units; no systemd/bwrap/cgroup prerequisite | `artifacts/tests/unit.xml` |
|
||||
| capability contract | `pnpm test:contract` | reusable capability consumer contracts | `artifacts/tests/contract.xml` |
|
||||
| component | `pnpm test:component` | React/hook/UI behavior | `artifacts/tests/component.xml` |
|
||||
| integration | `pnpm test:integration` | HTTP/MSW, IndexedDB and composed browser-runtime boundaries | `artifacts/tests/integration.xml` |
|
||||
| integration | `pnpm test:integration` | HTTP/MSW, IndexedDB, composed browser-runtime boundaries, child-process/filesystem/IPC integration | `artifacts/tests/integration.xml` |
|
||||
| system / CI runner | `pnpm test:system` | compatible Linux host with systemd, bubblewrap, cgroup v2 and CI-provider process controls | `artifacts/tests/system.xml` |
|
||||
| end-to-end | `pnpm test:e2e` | pinned browser engines | `artifacts/tests/e2e/` |
|
||||
| accessibility | `pnpm test:a11y` | pinned browser engines | `artifacts/tests/a11y.json` |
|
||||
@@ -64,7 +64,11 @@ CI / release assurance:
|
||||
|
||||
A host-level process/sandbox test must not be placed in `tests/unit` merely
|
||||
because it uses Vitest. The classification follows the system boundary and
|
||||
prerequisites, not the test framework.
|
||||
prerequisites, not the test framework. Process-heavy tests that spawn child
|
||||
processes but do not require privileged host facilities belong in
|
||||
`tests/integration`; for example the provider guardian transaction protocol
|
||||
lives at `tests/integration/provider-guardian-transaction.test.ts`. Its
|
||||
handshake timeout is a test watchdog, not a production protocol deadline.
|
||||
|
||||
End-to-end and automated accessibility scenarios run on the pinned Chromium,
|
||||
Firefox, and WebKit engines. The responsive contract explicitly exercises
|
||||
|
||||
Reference in New Issue
Block a user