Imported from fbuireu/forever-pto (
apps/web/src/domain/AGENTS.md). Install upstream withnpx skills add fbuireu/forever-pto --skill domain. Copyright stays with the author.
apps/web/src/domain
The business rules, in bounded contexts that deliberately do not follow the same rule. Nothing here
renders, routes, reads a request or reaches for a browser global. The vocabulary is
CONTEXT.md; a variable named for a retired term is a defect here, not a style
preference.
Bounded contexts
| Directory | Responsibility | Where it runs |
|---|---|---|
calendar/ |
The planning engine: Workday enumeration, Bridge detection, Strategy selection, Alternatives, Metrics | browser main thread and Web Worker |
payment/ |
Domain events for a Donation, and the Effect programs that handle them | server only |
They share no code and no types, and there is no reason for one to import the other. Premium is the only thing that connects them, and that connection lives in the application layer, not here.
Rival rules, on purpose
A reader who finds rival contracts inside one layer assumes one is a mistake. Both are intended; see ADR 0003.
calendar/ is pure. Its outside imports are a short list, and it is meant to stay that way:
@application/dto/holiday/types:HolidayDTOandHolidayVariant@application/shared/utils/dates: the Temporal-backed date helpers. The arrow points the wrong way and stays that way on purpose; the alternatives cost more than the tidiness is worth, and ADR 0012 records why. The test for a new entry on this list is the runtime, not the layer: does the module resolve inside a Web Worker with no DOM and no server contexttemporal-polyfill, inutils/helpers.tsonly, forPlainYearMonth.daysInMonthnext-intl, theLocaletype alone, threaded throughpipeline.tsto the Metrics, where it formats month names
No @infrastructure/*, no @ui/*, no Effect. The reason is the runtime rather than taste: the planner
evaluates this code inside a Web Worker with no DOM and no server context
(ADR 0001). An import that touches window,
process or a Node built-in breaks the planner in a way no server-side test will catch, because every
test in this repo runs on the main thread.
HolidayDTO living in the application layer is a layering inversion on paper, since the type describes a
Holiday, so it belongs here. It is a known exception, safe only because the file is types plus one const
object. See ../application/dto/AGENTS.md.
payment/ is not pure and is not meant to be. It composes Effect programs directly against
infrastructure service tags (@infrastructure/clients/*, @infrastructure/services/payments/*) and holds
import type Stripe in its event factory. The tags are interfaces, so tests substitute them without a
network, but the dependency on infrastructure is real, and naming it is better than pretending. Do not
"fix" it by extracting repository interfaces into the domain; ADR 0003 weighed that and rejected it.
Neither rule is enforced
There is no import-boundary rule in the Biome config. Both contracts hold by review or not at all. The
calendar one fails quietly: the bundle still builds, the worker throws at runtime, and
useCalculationsWorker only clears the loading flag on onerror, and there is no main-thread fallback, so
the user sees an empty plan and no error. That is the contract to guard.
Temporal never comes from the global
Temporal resolves from temporal-polyfill only, never the ambient global, because the global does not
exist in the deployed Workers runtime and a local run proves nothing
(ADR 0005). Almost all use here is indirect, through
@application/shared/utils/dates.
Testing
Every module with behaviour has a co-located .test.ts, run by Vitest. A few have none and should not grow
one: calendar/const.ts is a tunables object, payment/events/types.ts is types plus PAYMENT_SUCCEEDED,
and payment/events/factory/resolvers.ts is covered through events.test.ts. calendar/types.ts grew one:
it holds isFilterStrategy, the predicate the Web Worker narrows an incoming strategy string with, and
DEFAULT_FILTER_STRATEGY, the value both that fallback and the filters store's initial state read.
-
calendar/tests take literal inputs and assert on returned values; there is nothing to mock. Those whose subject reachesgetKeyorcreateHolidaySetmust callclearDateKeyCache()andclearHolidayCache()inbeforeEach: the caches incalendar/utils/cache.tsare module-level and survive between cases in the same file (ADR 0006). Themetrics/subtree reaches neither and is exempt; seecalendar/AGENTS.md. -
payment/tests build aLayer.succeed(Tag, mock)for every tag the handler requires and run the program over it. No test constructs a real Stripe or Turso client.