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
|
||||
|
||||
Reference in New Issue
Block a user