AI SLOP — an AI agent wrote this page. yuri4n, a senior engineer, gave the direction and did the review. The review is human, thus errors can stay.
Status
Accepted, 2026-08-04, decided by the project owner ("separate it as a library"). The name, the project shape, and the module split are implementation choices by Claude, reviewed with the rest of this change.
Context
The time layer became complete and self-contained: Intervals (the linear kernel), Point (calendar things), and Span (sets of instants) are fully implemented and tested, while the scheduling app around them is still a typed draft. The app consumes the layer only through types and a few calls. Nothing in the layer knows about activities, goals, or solvers. Code with that profile is a library.
Options
Option 1: keep one mix project
No structural change. Caveat: the boundary stays informal. App code can reach into kernel internals, and the time layer cannot be reused or versioned alone.
Option 2: an umbrella project
Restructure the repository into apps/s7r and apps/zocam. Caveat: umbrellas force shared configuration and a heavier layout onto a project with exactly two parts, one of which is a draft.
Option 3: a path dependency (chosen)
A standalone mix project in zocam/, consumed by the app as {:zocam, path: "zocam"}. The boundary is a real OTP application with its own tests and docs, but the code stays in one repository and one review flow. Publishing to Hex later is a version bump, not a restructure.
Decision
- The library is zocam: modules
Zocam.Intervals,Zocam.Point,Zocam.Span, plus theZocamoverview module. Intervalsmoves too.Span.ground/3produces kernel intervals andArcreuses the kernel's closing type. Splitting the tower would force a shared third package or duplicate types; one library carries all three layers.The kernel became calendar-free.
Intervals.timables/0narrowed toTime.t() | DateTime.t(). Weekday and month atoms could never be ordered by the comparison helpers, so they were data no operation could touch; the calendar vocabulary now lives inZocam.Point, andZocam.Span.ground/3is the path from "a Saturday" to concrete intervals.- These ADR sources live in
zocam/docs/and ship as ExDoc extras. The documentation site ingests them, so there is one source of truth per decision record.
flowchart LR
subgraph app["s7r (app, draft)"]
A[Activity · Goal · Preference · Context] --> S[Solver pipeline]
end
subgraph lib["zocam (library, implemented)"]
P[Zocam.Point] --> SP[Zocam.Span] --> I[Zocam.Intervals]
end
A -- "levels, availability: Span.t()" --> SP
S -- "grounded windows: Intervals.t()" --> IFigure 1 — The dependency direction after the split: the app points into the library, never back. AI generated, human reviewed.
Consequences
- Two test suites:
mix testin the repository root (app) and inzocam/(library). Both must stay green. - The app's abstract time fields (
Preference.levels,Context.availability) holdZocam.Span.t()values. Concrete placements (occurrences, events, solver windows) stayZocam.Intervalsvalues: the solver only ever sees grounded time. zocamruns no processes and holds no state. If a consumer needs caching (for example, memoized grounding), that consumer builds its own process layer on top of these pure functions.