refactor: 프론트 템플릿 리펙토링
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# Capability consumer experience baseline
|
||||
|
||||
Correctness inside a reusable capability is not sufficient. The platform is
|
||||
also evaluated by how much of that correctness a normal feature developer must
|
||||
understand.
|
||||
|
||||
These measurements are baselines, not score targets. A lower line count is not
|
||||
automatically better if it hides business semantics or creates a universal
|
||||
repository abstraction.
|
||||
|
||||
## Scenario A — reference REST feature
|
||||
|
||||
The existing reference feature is the executable REST consumer.
|
||||
|
||||
It covers list/detail/create behavior, mapping, typed operation inputs,
|
||||
application ports and an optimistic mutation path.
|
||||
|
||||
Current source size:
|
||||
|
||||
| Feature-owned area | LOC |
|
||||
| --- | ---: |
|
||||
| `application/reference-feature-api.ts` | 91 |
|
||||
| `contracts/reference-mapper.ts` | 109 |
|
||||
| `adapters/reference-http-gateway.ts` | 65 |
|
||||
| `adapters/create-reference-feature-input.ts` | 41 |
|
||||
| Total measured boundary/application source | 306 |
|
||||
|
||||
The important boundary metric is not the raw total. It is what those files need
|
||||
to know about the platform.
|
||||
|
||||
Current result:
|
||||
|
||||
- HTTP platform imports in the feature adapter layer: 2 files,
|
||||
- canonical import path used by both:
|
||||
`src/adapters/http/index.ts`,
|
||||
- direct imports of `http-execution-v3.ts`, retry scheduler, response reader,
|
||||
auth admission or effect-certainty internals: 0,
|
||||
- feature gateway owns transport failure projection: 0,
|
||||
- feature gateway chooses operation ID, route ID, exact input/value type and
|
||||
mapper: yes,
|
||||
- central installed files own feature-specific adapter/runtime wiring: no;
|
||||
feature-owned contributions are aggregated centrally.
|
||||
|
||||
The reusable HTTP capability now owns execution-outcome normalization through
|
||||
`createFeatureHttpBinding`. A feature may still implement a custom outbound
|
||||
adapter behind its application port when the reusable contract does not match
|
||||
its business requirement.
|
||||
|
||||
## Scenario B — IndexedDB local draft
|
||||
|
||||
An executable consumer probe lives at:
|
||||
|
||||
`tests/contract/consumer-experience/indexeddb-local-draft.test.ts`
|
||||
|
||||
with the feature-owned 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.
|
||||
|
||||
Measured result:
|
||||
|
||||
| Metric | Result |
|
||||
| --- | ---: |
|
||||
| feature-owned fixture LOC | 97 |
|
||||
| platform import statements | 1 |
|
||||
| native IndexedDB API references | 0 |
|
||||
| `src/adapters/storage/indexeddb/**` imports | 0 |
|
||||
| runtime-internal IndexedDB types imported by feature | 0 |
|
||||
|
||||
The feature depends only on the public application boundary:
|
||||
|
||||
`src/application/ports/browser-file-storage/index.ts`
|
||||
|
||||
and specifically `IndexedDbRepositoryPort<LocalDraft, never>`.
|
||||
|
||||
The contract test rejects feature source that reaches for
|
||||
`globalThis.indexedDB`, `IDBFactory`, `IDBDatabase`, `IDBTransaction`,
|
||||
`IDBObjectStore` or the concrete IndexedDB adapter directory.
|
||||
|
||||
## 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.
|
||||
|
||||
It does not prove that composition of `createIndexedDbRuntime` is cheap.
|
||||
That constructor still owns substantial infrastructure policy:
|
||||
|
||||
- dataset scope and storage policy,
|
||||
- physical store governance,
|
||||
- retention/idempotency stores,
|
||||
- schema migrations,
|
||||
- codec and query policy,
|
||||
- lifecycle authority,
|
||||
- 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.
|
||||
|
||||
## Consumer-quality review checklist
|
||||
|
||||
For each new product feature, record:
|
||||
|
||||
- feature-owned adapter LOC,
|
||||
- platform glue LOC,
|
||||
- files changed,
|
||||
- central catalog edits,
|
||||
- direct imports from capability-internal modules,
|
||||
- native browser/network API references,
|
||||
- duplicated failure/retry/lifecycle policy.
|
||||
|
||||
A healthy feature path should look like:
|
||||
|
||||
1. domain type and invariant,
|
||||
2. use case,
|
||||
3. business port,
|
||||
4. feature-owned mapper/codec and policy,
|
||||
5. capability-specific binding,
|
||||
6. presentation controller/page.
|
||||
|
||||
The feature should not need the retry scheduler, abort ownership,
|
||||
effect-certainty machinery, IndexedDB transaction lifecycle, OPFS journal,
|
||||
reconnect coordinator or provider process model.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Contract ownership
|
||||
|
||||
`src/contracts` is not a default destination for every shared-looking type.
|
||||
A contract belongs there only when the change authority is genuinely shared
|
||||
across layers, capabilities, build tooling, or runtime composition.
|
||||
|
||||
## Decision rule
|
||||
|
||||
For every proposed contract, ask:
|
||||
|
||||
1. Which requirement can cause this type or policy to change?
|
||||
2. Is there one clear capability or feature owner?
|
||||
3. Does another runtime/tooling boundary consume the same semantic contract?
|
||||
4. Is the contract a wire/artifact authority shared by browser runtime and
|
||||
build/release tooling?
|
||||
|
||||
The placement rule is:
|
||||
|
||||
- one feature owner -> keep it under that feature,
|
||||
- one reusable capability owner -> keep it under that adapter/capability and
|
||||
export it through the capability public entry point,
|
||||
- multiple independent capability/layer owners -> `src/contracts`,
|
||||
- browser/build/release wire or artifact authority -> `src/contracts` even
|
||||
when the browser source graph alone looks small.
|
||||
|
||||
Consumer count alone is not sufficient. Scripts, generated artifacts and
|
||||
release gates are semantic consumers too.
|
||||
|
||||
## Audit result
|
||||
|
||||
The September 2026 architecture review triggered an import-graph audit of all
|
||||
39 contract files.
|
||||
|
||||
### Capability-owned contract moved
|
||||
|
||||
`cursor-pagination.ts` had one production owner:
|
||||
`src/adapters/query-cache/cursor-pagination-runtime.ts`.
|
||||
|
||||
It moved to:
|
||||
|
||||
`src/adapters/query-cache/cursor-pagination-contract.ts`
|
||||
|
||||
and is exported through:
|
||||
|
||||
`src/adapters/query-cache/index.ts`
|
||||
|
||||
Tests use that public capability entry point. Pagination vocabulary no longer
|
||||
occupies the global contract bucket merely because it is reusable inside one
|
||||
adapter.
|
||||
|
||||
### Contracts intentionally kept global
|
||||
|
||||
The following examples have multiple semantic owners and remain global:
|
||||
|
||||
- `errors.ts` — application, presentation and several adapters,
|
||||
- `result.ts` — common success/failure carrier below application,
|
||||
- `boundary-mapper.ts` — HTTP, browser RPC, realtime and feature registries,
|
||||
- `mutation-intent.ts` — application, presentation, HTTP, platform and
|
||||
bootstrap,
|
||||
- `exact-snapshot.ts` — HTTP, query-cache and browser-transfer,
|
||||
- `rest-profiles.ts` — auth, HTTP, bootstrap and feature contract validation,
|
||||
- `query-invalidation.ts` / `query-keys.ts` — query-cache, presentation,
|
||||
bootstrap and features,
|
||||
- `cache-invalidation.ts` — cross-context wire protocol plus query
|
||||
invalidation policy,
|
||||
- `storage-keys.ts` — browser storage and cross-context invalidation,
|
||||
- `telemetry.ts` / `diagnostics.ts` — runtime adapters, application and
|
||||
bootstrap.
|
||||
|
||||
Some contracts appear to have few browser-source consumers but are still shared
|
||||
authorities:
|
||||
|
||||
- `env.ts` is consumed by bootstrap, runtime-schema/security tests and
|
||||
registry governance,
|
||||
- `deployment-admission.ts` is shared by runtime-config generation and release
|
||||
admission,
|
||||
- `release-tokens.ts` is shared by runtime coherence tooling and tests,
|
||||
- `service-worker-static-manifest.ts` is a runtime-neutral canonical format
|
||||
shared by build generation, validation and service-worker evidence.
|
||||
|
||||
Moving those based only on `src/**` import counts would split one semantic
|
||||
authority across processes.
|
||||
|
||||
## Feature contracts
|
||||
|
||||
Feature-specific contracts stay inside the vertical slice:
|
||||
|
||||
`features/<feature>/contracts`
|
||||
|
||||
The reference feature owns routes, schemas, mapper definitions, message
|
||||
catalogs and contribution identities. Central installed files aggregate those
|
||||
contributions; they do not own their semantics.
|
||||
|
||||
The message catalog is a deliberate special case: the compiled catalog remains
|
||||
total even when a feature is build-time disabled so the typed message lookup
|
||||
does not become partial. The central message file therefore aggregates compiled
|
||||
message keys rather than treating runtime installation as message ownership.
|
||||
|
||||
## Review rule for future additions
|
||||
|
||||
A new file under `src/contracts` should be rejected during review when all of
|
||||
the following are true:
|
||||
|
||||
- one feature or one capability is the only semantic owner,
|
||||
- no build/release/runtime wire authority needs the same definition,
|
||||
- moving the definition to that owner does not create an inward dependency
|
||||
violation.
|
||||
|
||||
Do not move a contract merely to reduce the number 39. The goal is explicit
|
||||
ownership, not a smaller directory.
|
||||
@@ -0,0 +1,219 @@
|
||||
# Frontend Application Foundation
|
||||
|
||||
This repository is not treated as a minimal React project template and it is not
|
||||
an independent general-purpose SDK. Its architectural role is:
|
||||
|
||||
> Frontend Application Foundation = Starter / Composition Skeleton + Reusable Capability Platform
|
||||
|
||||
The starter side owns bootstrap, routing, providers, project conventions and a
|
||||
removable reference feature. The capability side owns reusable technical
|
||||
problems such as HTTP execution, Server State, authentication boundaries,
|
||||
IndexedDB/OPFS, Cache Storage, realtime, browser RPC, transfer and diagnostics.
|
||||
|
||||
The cost of a sophisticated capability is acceptable only when product features
|
||||
do not have to understand that internal sophistication.
|
||||
|
||||
## Hybrid architecture
|
||||
|
||||
Platform code is horizontal:
|
||||
|
||||
- `application`: generic application inputs/policies/ports
|
||||
- `contracts`: genuinely cross-capability shared vocabulary and registries
|
||||
- `adapters`: reusable capability runtimes
|
||||
- `presentation`: generic UI/routing/query integration
|
||||
- `bootstrap`: concrete composition
|
||||
|
||||
Product business code is vertical:
|
||||
|
||||
- `features/<feature>/domain`
|
||||
- `features/<feature>/application`
|
||||
- `features/<feature>/contracts`
|
||||
- `features/<feature>/adapters`
|
||||
- `features/<feature>/presentation`
|
||||
|
||||
The dependency model is:
|
||||
|
||||
Domain / Use case
|
||||
|
|
||||
| owns
|
||||
v
|
||||
Business port
|
||||
^
|
||||
| implements / binds
|
||||
|
|
||||
Feature-owned adapter binding
|
||||
|
|
||||
| generic type + mapper/codec + policy
|
||||
v
|
||||
Reusable capability runtime
|
||||
|
|
||||
v
|
||||
Browser / network / native API
|
||||
|
||||
A use case never imports a platform adapter. Generic binding happens in the
|
||||
feature adapter/composition seam.
|
||||
|
||||
## Capability-specific typed bindings
|
||||
|
||||
Do not introduce one universal `Repository<TKey, TValue>` abstraction for HTTP,
|
||||
storage, realtime and transfer. Their lifecycle and failure semantics differ.
|
||||
|
||||
A reusable capability boundary is composed from:
|
||||
|
||||
- generic input/output types,
|
||||
- feature-owned mapper or codec,
|
||||
- feature-selected policy,
|
||||
- one capability-specific runtime.
|
||||
|
||||
The HTTP reference path is the first concrete example.
|
||||
`src/adapters/http/feature-http-binding.ts` owns transport/outcome
|
||||
normalization. The reference feature contributes only:
|
||||
|
||||
- operation ID,
|
||||
- route ID,
|
||||
- exact request input type,
|
||||
- exact success value type,
|
||||
- wire-to-domain mapper.
|
||||
|
||||
The feature gateway therefore does not reimplement timeout, cancellation,
|
||||
transport failure, authentication failure or contract-violation projection.
|
||||
|
||||
Storage, realtime and transfer may gain their own typed binders only after
|
||||
actual feature repetition demonstrates the need. They must not be forced
|
||||
through the HTTP abstraction.
|
||||
|
||||
## Custom adapter escape hatch
|
||||
|
||||
A product feature uses a reusable capability when the capability preserves the
|
||||
business requirement.
|
||||
|
||||
If a platform contract would require changing or weakening the business model,
|
||||
the feature implements a custom outbound adapter behind the same application
|
||||
port. The architecture boundary remains stable; platform reuse is optional.
|
||||
|
||||
## Feature installation
|
||||
|
||||
A feature owns its contract, runtime and adapter contributions.
|
||||
|
||||
Central installed catalogs are aggregation points only:
|
||||
|
||||
- `installed-product-manifest.ts`: which product features are compiled/selected
|
||||
- `installed-feature-contracts.ts`: contract aggregation
|
||||
- `installed-feature-runtimes.tsx`: runtime contribution aggregation
|
||||
- `installed-feature-adapters.ts`: application-input contribution aggregation
|
||||
|
||||
Feature-specific composition belongs under the feature itself. Central
|
||||
catalogs must not grow feature-specific branching logic.
|
||||
|
||||
## Presentation consumer surface
|
||||
|
||||
`ApplicationProvider` remains the composition root for presentation, but new
|
||||
consumers should not navigate a root `ApplicationApi` service locator.
|
||||
|
||||
Use the narrow hooks in
|
||||
`src/presentation/providers/application-provider.tsx`:
|
||||
|
||||
- `useApplicationSession`
|
||||
- `useApplicationPreferences`
|
||||
- `useApplicationDiagnostics`
|
||||
- `useApplicationRuntime`
|
||||
- `useApplicationRecovery`
|
||||
- `useApplicationFeature`
|
||||
|
||||
`useApplication` exists only as a deprecated compatibility escape hatch.
|
||||
|
||||
## Canonical imports
|
||||
|
||||
New feature code should use capability public entry points rather than deep
|
||||
runtime modules. For HTTP the canonical path is
|
||||
`src/adapters/http/index.ts`.
|
||||
|
||||
Compatibility re-exports may exist during a migration window, but they must be
|
||||
marked as compatibility/deprecated paths and should not expand into an
|
||||
unbounded public barrel.
|
||||
|
||||
## Policy ownership
|
||||
|
||||
Duplication is judged by ownership, not by syntax percentage.
|
||||
|
||||
Small local validators can remain duplicated when locality improves auditing.
|
||||
Business or concurrency policy must have one owner. For example the reference
|
||||
create mutation keeps definition ID, idempotency requirement, duplicate policy
|
||||
and invalidation policy in one feature-owned definition and binds only
|
||||
`scope` and `execute` per usage site.
|
||||
|
||||
## Runtime decomposition rule
|
||||
|
||||
Large runtime files are not split by line count.
|
||||
|
||||
Extract a boundary when it has its own state machine, lifecycle owner, failure
|
||||
model or compensation/recovery responsibility. Candidate seams include:
|
||||
|
||||
- connection/open/upgrade lifecycle,
|
||||
- transaction ownership,
|
||||
- migration state machine,
|
||||
- reconnect/backoff and heartbeat,
|
||||
- subscription ownership,
|
||||
- retry/deadline/cancellation ownership,
|
||||
- settlement/reconciliation/cleanup.
|
||||
|
||||
A cohesive 2,000-line state machine can remain together. A 300-line file with
|
||||
multiple lifecycle owners is a better extraction candidate.
|
||||
|
||||
### Current runtime boundary audit
|
||||
|
||||
The current large-runtime inventory was reviewed using that rule.
|
||||
|
||||
- IndexedDB remains large, but connection, transaction, migration, maintenance
|
||||
and failure translation already have separate owners/modules.
|
||||
- resumable upload already separates runtime policy, checkpoint persistence,
|
||||
HTTP control-plane transport, part execution, cancellation and mutation
|
||||
locking.
|
||||
- realtime already separates reconnect policy/coordinator, event codec/consumer
|
||||
and stream coordination.
|
||||
- OPFS is separated into browser runtime, journal, byte-store, policy and worker
|
||||
protocol/runtime responsibilities.
|
||||
- HTTP V3 still owned retry eligibility/backoff inside the execution state
|
||||
machine, so that responsibility moved to
|
||||
`src/adapters/http/http-retry-lifecycle.ts`.
|
||||
|
||||
No other runtime is split merely because of its line count.
|
||||
|
||||
## Consumer quality metrics
|
||||
|
||||
Before adding another abstraction, implement or model multiple real feature
|
||||
uses and measure:
|
||||
|
||||
- feature-owned adapter LOC,
|
||||
- repeated platform glue,
|
||||
- number of platform-internal types exposed to the feature,
|
||||
- central catalog edits,
|
||||
- files changed for one normal query/command,
|
||||
- whether native browser/network APIs leak into the feature.
|
||||
|
||||
The target feature-development path is:
|
||||
|
||||
1. domain type and invariant,
|
||||
2. use case,
|
||||
3. port,
|
||||
4. transport/storage schema plus mapper/codec,
|
||||
5. capability binding,
|
||||
6. presentation controller/page.
|
||||
|
||||
A product feature should not need to know the retry scheduler, abort ownership,
|
||||
effect-certainty machinery, transaction leases, reconnect coordinator, OPFS
|
||||
journal or provider lifecycle.
|
||||
|
||||
The executable REST and IndexedDB consumer baselines are recorded in
|
||||
[`capability-consumer-experience.md`](./capability-consumer-experience.md).
|
||||
Contract placement and the global-vs-owner-local audit are recorded in
|
||||
[`contract-ownership.md`](./contract-ownership.md).
|
||||
|
||||
## Verification paths
|
||||
|
||||
Product-development verification, capability verification and release assurance
|
||||
are intentionally separate. See
|
||||
[`docs/testing/taxonomy.md`](../testing/taxonomy.md).
|
||||
|
||||
Host-level CI-runner tests belong to `tests/system`, not `tests/unit`.
|
||||
Reusable capability consumer contracts belong to `tests/contract`.
|
||||
Reference in New Issue
Block a user