[{"data":1,"prerenderedAt":470},["ShallowReactive",2],{"navigation_docs":3,"-design-adrs-001-refinement-chain":101,"-design-adrs-001-refinement-chain-surround":466},[4,23,66],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":22},"Guide","i-lucide-book-open","\u002Fguide","1.guide",[10,14,18],{"title":11,"path":12,"stem":13},"Introduction","\u002Fguide\u002Fintroduction","1.guide\u002F1.introduction",{"title":15,"path":16,"stem":17},"Getting started","\u002Fguide\u002Fgetting-started","1.guide\u002F2.getting-started",{"title":19,"path":20,"stem":21},"Pieces and sets","\u002Fguide\u002Fpieces-and-sets","1.guide\u002F3.pieces-and-sets",false,{"title":24,"icon":25,"path":26,"stem":27,"children":28},"Design (ADRs)","i-lucide-drafting-compass","\u002Fdesign","2.design",[29,32],{"title":30,"path":26,"stem":31},"Design decisions (ADRs)","2.design\u002Findex",{"title":33,"icon":34,"path":35,"stem":36,"children":37,"page":22},"Decision records","i-lucide-scroll-text","\u002Fdesign\u002Fadrs","2.design\u002Fadrs",[38,42,46,50,54,58,62],{"title":39,"path":40,"stem":41},"ADR-001: The refinement-chain model for calendar points","\u002Fdesign\u002Fadrs\u002F001-refinement-chain","2.design\u002Fadrs\u002F001-refinement-chain",{"title":43,"path":44,"stem":45},"ADR-002: Sets first — the Span algebra","\u002Fdesign\u002Fadrs\u002F002-set-primary-spans","2.design\u002Fadrs\u002F002-set-primary-spans",{"title":47,"path":48,"stem":49},"ADR-003: Overflow policy — clamp by default, skip on request","\u002Fdesign\u002Fadrs\u002F003-overflow-policy","2.design\u002Fadrs\u002F003-overflow-policy",{"title":51,"path":52,"stem":53},"ADR-004: Fortnights as every-k scopes","\u002Fdesign\u002Fadrs\u002F004-fortnight-scopes","2.design\u002Fadrs\u002F004-fortnight-scopes",{"title":55,"path":56,"stem":57},"ADR-005: One denotation function, and what DST does to it","\u002Fdesign\u002Fadrs\u002F005-shared-denotation","2.design\u002Fadrs\u002F005-shared-denotation",{"title":59,"path":60,"stem":61},"ADR-006: Zocam is a standalone, released library","\u002Fdesign\u002Fadrs\u002F006-standalone-library","2.design\u002Fadrs\u002F006-standalone-library",{"title":63,"path":64,"stem":65},"ADR-007: A set is one value — struct answers, enumerable shapes","\u002Fdesign\u002Fadrs\u002F007-one-value-sets","2.design\u002Fadrs\u002F007-one-value-sets",{"title":67,"path":68,"stem":69,"children":70,"icon":100},"API Reference","\u002Fapi","3.api\u002Findex",[71,72,76,80,84,88,92,96],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75},"Zocam","\u002Fapi\u002Fzocam","3.api\u002Fzocam",{"title":77,"path":78,"stem":79},"Zocam.Intervals","\u002Fapi\u002Fzocam-intervals","3.api\u002Fzocam-intervals",{"title":81,"path":82,"stem":83},"Zocam.ISO","\u002Fapi\u002Fzocam-iso","3.api\u002Fzocam-iso",{"title":85,"path":86,"stem":87},"Zocam.Point","\u002Fapi\u002Fzocam-point","3.api\u002Fzocam-point",{"title":89,"path":90,"stem":91},"Zocam.Point.ComposeError","\u002Fapi\u002Fzocam-point-composeerror","3.api\u002Fzocam-point-composeerror",{"title":93,"path":94,"stem":95},"Zocam.Span","\u002Fapi\u002Fzocam-span","3.api\u002Fzocam-span",{"title":97,"path":98,"stem":99},"Zocam.Span.Arc","\u002Fapi\u002Fzocam-span-arc","3.api\u002Fzocam-span-arc","i-lucide-braces",{"id":102,"title":39,"body":103,"description":131,"extension":460,"links":461,"meta":462,"navigation":463,"path":40,"seo":464,"stem":41,"__hash__":465},"docs\u002F2.design\u002Fadrs\u002F001-refinement-chain.md",{"type":104,"value":105,"toc":449},"minimark",[106,124,129,132,136,139,142,146,151,173,192,196,199,202,206,230,234,240,262,320,325,379,383,386,404,407,422,425,445],[107,108,109,115,116,123],"p",{},[110,111,114],"span",{"className":112},[113],"ai-slop","AI SLOP"," an AI agent wrote this page. ",[117,118,122],"a",{"href":119,"rel":120},"https:\u002F\u002Fgithub.com\u002Fyuri4n",[121],"nofollow","yuri4n",", a senior engineer, gave the direction and did the review. The review is human, thus errors can stay.",[125,126,128],"h2",{"id":127},"status","Status",[107,130,131],{},"Accepted, 2026-08-04, decided by the project owner.",[125,133,135],{"id":134},"context","Context",[107,137,138],{},"Zocam must represent partial calendar values: \"May\", \"15:00\", \"the 23rd\", \"May 2026\", \"a Wednesday\". These values are all \"things about time\", but they differ in two ways. Some repeat (\"May\" returns each year) and some do not (\"May 2026\"). Some name a month, some a day, some a clock time.",[107,140,141],{},"One data type must serve all of them. The type must support two operations. First, composition: \"May\" plus \"the 23rd\" gives \"May 23rd\". Second, grounding: a value must map to real intervals on the timeline. The type must also reject nonsense, such as \"May of June\", as early as possible.",[125,143,145],{"id":144},"options","Options",[147,148,150],"h3",{"id":149},"option-1-field-mask-model","Option 1: field-mask model",[107,152,153,154,158,159,158,162,158,165,168,169,172],{},"One struct with an optional field per unit: ",[155,156,157],"code",{},"year",", ",[155,160,161],{},"month",[155,163,164],{},"day",[155,166,167],{},"time",". A ",[155,170,171],{},"nil"," field means \"not bound\".",[107,174,175,176,180,181,184,185,188,189,191],{},"Caveat: the struct cannot say ",[177,178,179],"em",{},"which"," units are bound without ad-hoc rules. Is ",[155,182,183],{},"%{month: :may, time: nil}"," a month or a set of instants? Worse, weeks do not nest in months, so a ",[155,186,187],{},"week"," field and a ",[155,190,161],{}," field would conflict in the same struct. This is the anemic-record anti-pattern: the data holds values, but the meaning lives in scattered checks.",[147,193,195],{"id":194},"option-2-combinator-model","Option 2: combinator model",[107,197,198],{},"A free-form expression tree of combinators, like a parser combinator library. This is the interpreter pattern applied too early.",[107,200,201],{},"Caveat: many meaningless expressions type-check. \"May of June\" builds a valid tree. Every consumer must then validate at runtime, so validation spreads through the whole codebase instead of living in one place.",[147,203,205],{"id":204},"option-3-refinement-chain-model-chosen","Option 3: refinement-chain model (chosen)",[107,207,208,209,212,213,158,216,158,219,158,222,225,226,229],{},"A point is a pair ",[155,210,211],{},"{scope, chain}",". The scope is the cycle in which the point repeats: ",[155,214,215],{},":absolute",[155,217,218],{},":year",[155,220,221],{},":month",[155,223,224],{},":week",", or ",[155,227,228],{},":day",". The chain is a list of contiguous valued segments. It starts directly under the scope and descends the unit graph, one unit at a time, to a grain.",[125,231,233],{"id":232},"decision","Decision",[107,235,236,237,239],{},"The chosen model is the refinement chain. ",[155,238,85],{}," implements it. The precise rules:",[241,242,243,259],"ul",{},[244,245,246,247,250,251,254,255,258],"li",{},"Concrete and abstract are not two types. A point is concrete exactly when ",[155,248,249],{},"scope == :absolute",". \"May\" is ",[155,252,253],{},"{scope: :year, chain: [month: :may]}","; \"May 2026\" is ",[155,256,257],{},"{scope: :absolute, chain: [year: 2026, month: :may]}",".",[244,260,261],{},"Chains must follow the edges of the unit graph. The week branch is a sibling of the month branch, because weeks do not nest in months.",[263,264,269],"pre",{"className":265,"code":266,"language":267,"meta":268,"style":268},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","graph LR\n  A[\":absolute\"] --> Y[year]\n  Y --> M[month]\n  M --> D[day]\n  D --> T[time]\n  Y --> W[\"week (1..53)\"]\n  W --> WD[weekday]\n  WD --> T\n","mermaid","",[155,270,271,278,284,290,296,302,308,314],{"__ignoreMap":268},[110,272,275],{"class":273,"line":274},"line",1,[110,276,277],{},"graph LR\n",[110,279,281],{"class":273,"line":280},2,[110,282,283],{},"  A[\":absolute\"] --> Y[year]\n",[110,285,287],{"class":273,"line":286},3,[110,288,289],{},"  Y --> M[month]\n",[110,291,293],{"class":273,"line":292},4,[110,294,295],{},"  M --> D[day]\n",[110,297,299],{"class":273,"line":298},5,[110,300,301],{},"  D --> T[time]\n",[110,303,305],{"class":273,"line":304},6,[110,306,307],{},"  Y --> W[\"week (1..53)\"]\n",[110,309,311],{"class":273,"line":310},7,[110,312,313],{},"  W --> WD[weekday]\n",[110,315,317],{"class":273,"line":316},8,[110,318,319],{},"  WD --> T\n",[107,321,322],{},[177,323,324],{},"Figure 1 — The unit graph: the edges a chain can follow from a scope down to a grain. AI generated, human reviewed.",[241,326,327,351,354,369],{},[244,328,329,332,333,337,338,340,341,344,345,347,348,350],{},[155,330,331],{},"compose\u002F2"," concatenates two chains. It is defined only when the inner scope ",[334,335,336],"strong",{},"class"," equals the outer grain ",[334,339,336],{},". Classes matter: ",[155,342,343],{},"weekday"," and ",[155,346,164],{}," are different units with the same class, ",[155,349,228],{},". A raw atom comparison would make \"a Wednesday at 15:00\" unreachable.",[244,352,353],{},"Composition is arrow composition in a category. In simple terms: units are the objects, points are the arrows, and two arrows compose only where they meet.",[244,355,356,357,360,361,364,365,368],{},"When the guard fails, the meaning is usually a ",[177,358,359],{},"set",", not a point. \"15:00 of May\" leaves the day free. For these sets, use ",[155,362,363],{},"Zocam.Span.intersection\u002F1",". The ",[155,366,367],{},"ComposeError"," hint names this function.",[244,370,371,372,375,376,258],{},"Fortnight-like rhythms live in the scope as ",[155,373,374],{},"{:every, k, cycle, anchor}",". See ",[117,377,378],{"href":52},"ADR-004",[125,380,382],{"id":381},"consequences","Consequences",[107,384,385],{},"Easy now:",[241,387,388,395,398],{},[244,389,390,391,394],{},"One validation walk in ",[155,392,393],{},"Point.new!\u002F1"," checks every chain. All other functions trust it.",[244,396,397],{},"Composition is total where it is defined, and each failure has a typed reason and a repair hint.",[244,399,400,401,403],{},"The set layer stays separate: ",[155,402,93],{}," lifts points and handles everything a chain cannot say.",[107,405,406],{},"Hard now:",[241,408,409,419],{},[244,410,411,412,344,415,418],{},"Two type axes (scope class, grain class) take time to learn. The readers ",[155,413,414],{},"grain_class\u002F1",[155,416,417],{},"scope_class\u002F1"," expose them.",[244,420,421],{},"The unit graph is fixed. A new unit (for example, quarters) needs a new edge and a new class decision.",[107,423,424],{},"Standing invariant:",[241,426,427],{},[244,428,429,432,433,436,437,440,441,444],{},[155,430,431],{},"Span.of\u002F1"," and the set operators are implemented. The core property — ",[155,434,435],{},"member?\u002F2"," agrees with ",[155,438,439],{},"ground\u002F3"," — stays guarded by the tests in ",[155,442,443],{},"test\u002Fzocam\u002Fspan_test.exs",". This is not an open item; it is a rule that each later change must keep.",[446,447,448],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":268,"searchDepth":280,"depth":280,"links":450},[451,452,453,458,459],{"id":127,"depth":280,"text":128},{"id":134,"depth":280,"text":135},{"id":144,"depth":280,"text":145,"children":454},[455,456,457],{"id":149,"depth":286,"text":150},{"id":194,"depth":286,"text":195},{"id":204,"depth":286,"text":205},{"id":232,"depth":280,"text":233},{"id":381,"depth":280,"text":382},"md",null,{},true,{"title":39,"description":131},"Hgxf0wd8oUuKVaR-Ggg2jbUHX4c1bekzTAsvRR8RFvQ",[467,469],{"title":30,"path":26,"stem":31,"description":468,"children":-1},"One record for each irreversible design choice in the zocam time library, with the reasoning behind it.",{"title":43,"path":44,"stem":45,"description":131,"children":-1},1786065169170]