[{"data":1,"prerenderedAt":843},["ShallowReactive",2],{"navigation_docs_en":3,"-en-development-date-utilities":191,"-en-development-date-utilities-surround":838},[4,61,127,171],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":60},"Guide","i-lucide-book-open","\u002Fen\u002Fguide","en\u002F1.guide",[10,15,20,25,30,35,40,45,50,55],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fguide\u002Fintroduction","en\u002F1.guide\u002F01.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Install the App","\u002Fen\u002Fguide\u002Finstallation","en\u002F1.guide\u002F02.installation","i-lucide-smartphone",{"title":21,"path":22,"stem":23,"icon":24},"Authentication","\u002Fen\u002Fguide\u002Fauth","en\u002F1.guide\u002F03.auth","i-lucide-lock",{"title":26,"path":27,"stem":28,"icon":29},"Wallets","\u002Fen\u002Fguide\u002Fwallets","en\u002F1.guide\u002F04.wallets","i-lucide-wallet",{"title":31,"path":32,"stem":33,"icon":34},"Categories","\u002Fen\u002Fguide\u002Fcategories","en\u002F1.guide\u002F05.categories","i-lucide-tags",{"title":36,"path":37,"stem":38,"icon":39},"Transactions","\u002Fen\u002Fguide\u002Ftransactions","en\u002F1.guide\u002F06.transactions","i-lucide-receipt",{"title":41,"path":42,"stem":43,"icon":44},"Transfers","\u002Fen\u002Fguide\u002Ftransfers","en\u002F1.guide\u002F07.transfers","i-lucide-arrow-left-right",{"title":46,"path":47,"stem":48,"icon":49},"Statistics","\u002Fen\u002Fguide\u002Fstatistics","en\u002F1.guide\u002F08.statistics","i-lucide-bar-chart-3",{"title":51,"path":52,"stem":53,"icon":54},"Theme","\u002Fen\u002Fguide\u002Ftheme","en\u002F1.guide\u002F09.theme","i-lucide-palette",{"title":56,"path":57,"stem":58,"icon":59},"Settings","\u002Fen\u002Fguide\u002Fsettings","en\u002F1.guide\u002F10.settings","i-lucide-settings",false,{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":60},"Development","i-lucide-code","\u002Fen\u002Fdevelopment","en\u002F2.development",[67,72,77,82,87,92,97,102,122],{"title":68,"path":69,"stem":70,"icon":71},"Installation","\u002Fen\u002Fdevelopment\u002Finstallation","en\u002F2.development\u002F01.installation","i-lucide-download",{"title":73,"path":74,"stem":75,"icon":76},"Codebase Graph","\u002Fen\u002Fdevelopment\u002Funderstand-anything","en\u002F2.development\u002F02.understand-anything","i-lucide-network",{"title":78,"path":79,"stem":80,"icon":81},"Offline & PWA","\u002Fen\u002Fdevelopment\u002Foffline","en\u002F2.development\u002F03.offline","i-lucide-wifi-off",{"title":83,"path":84,"stem":85,"icon":86},"Data Migration History","\u002Fen\u002Fdevelopment\u002Fmigration","en\u002F2.development\u002F04.migration","i-lucide-database",{"title":88,"path":89,"stem":90,"icon":91},"Deployment","\u002Fen\u002Fdevelopment\u002Fdeployment","en\u002F2.development\u002F05.deployment","i-lucide-rocket",{"title":93,"path":94,"stem":95,"icon":96},"Testing","\u002Fen\u002Fdevelopment\u002Ftesting","en\u002F2.development\u002F06.testing","i-lucide-flask-conical",{"title":98,"path":99,"stem":100,"icon":101},"Date Utilities","\u002Fen\u002Fdevelopment\u002Fdate-utilities","en\u002F2.development\u002F07.date-utilities","i-lucide-calendar",{"title":103,"path":104,"stem":105,"children":106,"page":60},"Ai Workflow","\u002Fen\u002Fdevelopment\u002Fai-workflow","en\u002F2.development\u002F08.ai-workflow",[107,112,117],{"title":108,"path":109,"stem":110,"icon":111},"Overview","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Foverview","en\u002F2.development\u002F08.ai-workflow\u002F01.overview","i-lucide-bot",{"title":113,"path":114,"stem":115,"icon":116},"Agents","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fagents","en\u002F2.development\u002F08.ai-workflow\u002F02.agents","i-lucide-users",{"title":118,"path":119,"stem":120,"icon":121},"Skills","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fskills","en\u002F2.development\u002F08.ai-workflow\u002F03.skills","i-lucide-lightbulb",{"title":123,"path":124,"stem":125,"icon":126},"Troubleshooting","\u002Fen\u002Fdevelopment\u002Ftroubleshooting","en\u002F2.development\u002F09.troubleshooting","i-lucide-life-buoy",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":60},"Reference","i-lucide-file-code","\u002Fen\u002Freference","en\u002F3.reference",[133,138,142,147,152,156,161,166],{"title":134,"path":135,"stem":136,"icon":137},"Architecture","\u002Fen\u002Freference\u002Farchitecture","en\u002F3.reference\u002F01.architecture","i-lucide-boxes",{"title":139,"path":140,"stem":141,"icon":44},"Transaction Types","\u002Fen\u002Freference\u002Ftransaction-types","en\u002F3.reference\u002F02.transaction-types",{"title":143,"path":144,"stem":145,"icon":146},"Sync","\u002Fen\u002Freference\u002Fsync","en\u002F3.reference\u002F03.sync","i-lucide-refresh-cw",{"title":148,"path":149,"stem":150,"icon":151},"Offline-first","\u002Fen\u002Freference\u002Foffline-first","en\u002F3.reference\u002F04.offline-first","i-lucide-list-ordered",{"title":153,"path":154,"stem":155,"icon":121},"Technical Decisions","\u002Fen\u002Freference\u002Ftech-decisions","en\u002F3.reference\u002F05.tech-decisions",{"title":157,"path":158,"stem":159,"icon":160},"Validation Strategy","\u002Fen\u002Freference\u002Fvalidation-strategy","en\u002F3.reference\u002F06.validation-strategy","i-lucide-shield-check",{"title":162,"path":163,"stem":164,"icon":165},"What Changed Since Firebase","\u002Fen\u002Freference\u002Ffirebase-migration","en\u002F3.reference\u002F07.firebase-migration","i-lucide-hamburger",{"title":167,"path":168,"stem":169,"icon":170},"Performance","\u002Fen\u002Freference\u002Fperformance","en\u002F3.reference\u002F08.performance","i-lucide-gauge",{"title":172,"icon":173,"path":174,"stem":175,"children":176,"page":60},"Premium","i-lucide-star","\u002Fen\u002Fpremium","en\u002F4.premium",[177,181,186],{"title":108,"path":178,"stem":179,"icon":180},"\u002Fen\u002Fpremium\u002Foverview","en\u002F4.premium\u002F01.overview","i-lucide-layers",{"title":182,"path":183,"stem":184,"icon":185},"Telegram Bot","\u002Fen\u002Fpremium\u002Ftelegram-bot","en\u002F4.premium\u002F02.telegram-bot","i-lucide-send",{"title":187,"path":188,"stem":189,"icon":190},"AI Chat","\u002Fen\u002Fpremium\u002Fai-chat","en\u002F4.premium\u002F03.ai-chat","i-lucide-sparkles",{"id":192,"title":98,"body":193,"description":830,"extension":831,"links":832,"meta":833,"navigation":834,"path":99,"seo":835,"stem":100,"__hash__":837},"docs_en\u002Fen\u002F2.development\u002F07.date-utilities.md",{"type":194,"value":195,"toc":815},"minimark",[196,200,205,309,313,319,415,421,473,480,485,494,579,583,596,604,626,630,636,642,645,648,675,679,685,716,719,723,737,743,750,754,775,779,792,796,811],[197,198,199],"p",{},"Date utilities handle period selection, range computation, interval generation, and date formatting for statistics.",[201,202,204],"h2",{"id":203},"key-files","Key Files",[206,207,208,221],"table",{},[209,210,211],"thead",{},[212,213,214,218],"tr",{},[215,216,217],"th",{},"File",[215,219,220],{},"Purpose",[222,223,224,247,269,279,289,299],"tbody",{},[212,225,226,233],{},[227,228,229],"td",{},[230,231,232],"code",{},"app\u002Fcomponents\u002Fdate\u002Futils.ts",[227,234,235,236,239,240,239,243,246],{},"Core utilities: ",[230,237,238],{},"toDuration",", ",[230,241,242],{},"getStartOf",[230,244,245],{},"getEndOf",", interval computation",[212,248,249,254],{},[227,250,251],{},[230,252,253],{},"app\u002Fcomponents\u002Fdate\u002Ftypes.ts",[227,255,256,257,239,260,239,263,239,266],{},"Types: ",[230,258,259],{},"Period",[230,261,262],{},"Range",[230,264,265],{},"StatDateParams",[230,267,268],{},"IntervalsInRangeProps",[212,270,271,276],{},[227,272,273],{},[230,274,275],{},"app\u002Fcomponents\u002Fdate\u002FstatDateParams.ts",[227,277,278],{},"Range computation from params, query param parsing",[212,280,281,286],{},[227,282,283],{},[230,284,285],{},"app\u002Fcomponents\u002Fdate\u002FuseStatDate.ts",[227,287,288],{},"Composable: reactive date state with localStorage persistence",[212,290,291,296],{},[227,292,293],{},[230,294,295],{},"app\u002Fcomponents\u002Fstat\u002Fdate\u002FuseGetDateRange.ts",[227,297,298],{},"Human-readable date labels (\"Today\", \"Last 3 months\", \"Mar - Jun\")",[212,300,301,306],{},[227,302,303],{},[230,304,305],{},"app\u002Fcomponents\u002Fstat\u002Fintervals.ts",[227,307,308],{},"Transaction bucketing into intervals, averages",[201,310,312],{"id":311},"period-system","Period System",[197,314,315,316,318],{},"All date operations use the ",[230,317,259],{}," union type:",[320,321,326],"pre",{"className":322,"code":323,"language":324,"meta":325,"style":325},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const periods = ['day', 'week', 'month', 'year'] as const\ntype Period = typeof periods[number]\n","ts","",[230,327,328,396],{"__ignoreMap":325},[329,330,333,337,341,345,348,351,355,357,360,363,366,368,370,372,375,377,379,381,384,386,389,393],"span",{"class":331,"line":332},"line",1,[329,334,336],{"class":335},"spNyl","const",[329,338,340],{"class":339},"sTEyZ"," periods ",[329,342,344],{"class":343},"sMK4o","=",[329,346,347],{"class":339}," [",[329,349,350],{"class":343},"'",[329,352,354],{"class":353},"sfazB","day",[329,356,350],{"class":343},[329,358,359],{"class":343},",",[329,361,362],{"class":343}," '",[329,364,365],{"class":353},"week",[329,367,350],{"class":343},[329,369,359],{"class":343},[329,371,362],{"class":343},[329,373,374],{"class":353},"month",[329,376,350],{"class":343},[329,378,359],{"class":343},[329,380,362],{"class":343},[329,382,383],{"class":353},"year",[329,385,350],{"class":343},[329,387,388],{"class":339},"] ",[329,390,392],{"class":391},"s7zQu","as",[329,394,395],{"class":335}," const\n",[329,397,399,402,406,409,412],{"class":331,"line":398},2,[329,400,401],{"class":335},"type",[329,403,405],{"class":404},"sBMFI"," Period",[329,407,408],{"class":343}," =",[329,410,411],{"class":343}," typeof",[329,413,414],{"class":339}," periods[number]\n",[197,416,417,418,420],{},"Three core helpers convert ",[230,419,259],{}," into date-fns operations:",[320,422,424],{"className":322,"code":423,"language":324,"meta":325,"style":325},"toDuration(period, value)   \u002F\u002F Period -> Duration ({ days: 3 }, { months: 1 })\ngetStartOf(date, period)    \u002F\u002F Period -> start boundary (startOfMonth, startOfWeek, ...)\ngetEndOf(date, period)      \u002F\u002F Period -> end boundary (endOfMonth, endOfWeek, ...)\n",[230,425,426,443,458],{"__ignoreMap":325},[329,427,428,431,434,436,439],{"class":331,"line":332},[329,429,238],{"class":430},"s2Zo4",[329,432,433],{"class":339},"(period",[329,435,359],{"class":343},[329,437,438],{"class":339}," value)   ",[329,440,442],{"class":441},"sHwdD","\u002F\u002F Period -> Duration ({ days: 3 }, { months: 1 })\n",[329,444,445,447,450,452,455],{"class":331,"line":398},[329,446,242],{"class":430},[329,448,449],{"class":339},"(date",[329,451,359],{"class":343},[329,453,454],{"class":339}," period)    ",[329,456,457],{"class":441},"\u002F\u002F Period -> start boundary (startOfMonth, startOfWeek, ...)\n",[329,459,461,463,465,467,470],{"class":331,"line":460},3,[329,462,245],{"class":430},[329,464,449],{"class":339},[329,466,359],{"class":343},[329,468,469],{"class":339}," period)      ",[329,471,472],{"class":441},"\u002F\u002F Period -> end boundary (endOfMonth, endOfWeek, ...)\n",[197,474,475,476,479],{},"All three use exhaustive ",[230,477,478],{},"switch"," - adding a new period variant causes a compile error, not silent fallback.",[481,482,484],"h3",{"id":483},"type-safe-duration","Type-safe Duration",[197,486,487,489,490,493],{},[230,488,238],{}," replaces dynamic property keys with a proper ",[230,491,492],{},"Duration"," return type:",[320,495,497],{"className":322,"code":496,"language":324,"meta":325,"style":325},"\u002F\u002F Before: loses type safety, TS sees Record\u003Cstring, number>\nsub(date, { [`${period}s`]: value })\n\n\u002F\u002F After: returns typed Duration\nsub(date, toDuration(period, value))\n",[230,498,499,504,548,554,560],{"__ignoreMap":325},[329,500,501],{"class":331,"line":332},[329,502,503],{"class":441},"\u002F\u002F Before: loses type safety, TS sees Record\u003Cstring, number>\n",[329,505,506,509,511,513,516,519,522,525,528,531,534,537,540,543,545],{"class":331,"line":398},[329,507,508],{"class":430},"sub",[329,510,449],{"class":339},[329,512,359],{"class":343},[329,514,515],{"class":343}," {",[329,517,347],{"class":518},"swJcz",[329,520,521],{"class":343},"`${",[329,523,524],{"class":339},"period",[329,526,527],{"class":343},"}",[329,529,530],{"class":518},"s",[329,532,533],{"class":343},"`",[329,535,536],{"class":518},"]",[329,538,539],{"class":343},":",[329,541,542],{"class":339}," value ",[329,544,527],{"class":343},[329,546,547],{"class":339},")\n",[329,549,550],{"class":331,"line":460},[329,551,553],{"emptyLinePlaceholder":552},true,"\n",[329,555,557],{"class":331,"line":556},4,[329,558,559],{"class":441},"\u002F\u002F After: returns typed Duration\n",[329,561,563,565,567,569,572,574,576],{"class":331,"line":562},5,[329,564,508],{"class":430},[329,566,449],{"class":339},[329,568,359],{"class":343},[329,570,571],{"class":430}," toDuration",[329,573,433],{"class":339},[329,575,359],{"class":343},[329,577,578],{"class":339}," value))\n",[201,580,582],{"id":581},"date-range-computation","Date Range Computation",[197,584,585,588,589,591,592,595],{},[230,586,587],{},"computeDateRange()"," resolves ",[230,590,265],{}," into a concrete ",[230,593,594],{},"{ start, end }"," range:",[320,597,602],{"className":598,"code":600,"language":601},[599],"language-text","Priority:\n1. customDate (calendar picker)      -> return as-is\n2. isShowMaxRange                    -> maxRange.start to end-of-current-period\n3. isShowMaxRange + isSkipEmpty      -> maxRange as-is\n4. calculated                        -> from Date.now() using rangeBy\u002FrangeDuration\u002FrangeOffset\n","text",[230,603,600],{"__ignoreMap":325},[197,605,606,607,610,611,614,615,617,618,621,622,625],{},"The range is a Vue ",[230,608,609],{},"computed"," that uses ",[230,612,613],{},"Date.now()"," as the base. Since ",[230,616,613],{}," is not reactive, the range updates only when ",[230,619,620],{},"params"," or ",[230,623,624],{},"maxRange"," change (not automatically at midnight). Page refresh or any param change triggers recomputation.",[201,627,629],{"id":628},"interval-generation","Interval Generation",[197,631,632,635],{},[230,633,634],{},"getIntervalsInRange()"," splits a date range into sub-intervals:",[320,637,640],{"className":638,"code":639,"language":601},[599],"range: Mar 1 - Mar 31, intervalsBy: 'week'\n-> [Mar 1-2, Mar 3-9, Mar 10-16, Mar 17-23, Mar 24-30, Mar 31]\n",[230,641,639],{"__ignoreMap":325},[197,643,644],{},"Edge intervals are clamped to the range boundaries.",[481,646,167],{"id":647},"performance",[197,649,650,651,654,655,658,659,662,663,666,667,670,671,674],{},"Intervals are collected with ",[230,652,653],{},"push()"," then ",[230,656,657],{},"reverse()"," instead of ",[230,660,661],{},"unshift()"," in a loop. ",[230,664,665],{},"unshift"," shifts all elements on every call (O(n^2) for the loop), while ",[230,668,669],{},"push"," + ",[230,672,673],{},"reverse"," is O(n).",[201,676,678],{"id":677},"transaction-bucketing","Transaction Bucketing",[197,680,681,684],{},[230,682,683],{},"bucketTrnsByIntervals()"," assigns transactions to intervals using binary search:",[320,686,688],{"className":322,"code":687,"language":324,"meta":325,"style":325},"\u002F\u002F O(N log M) where N = transactions, M = intervals\nfor each transaction:\n  binary search intervals by date\n  push into matching bucket\n",[230,689,690,695,706,711],{"__ignoreMap":325},[329,691,692],{"class":331,"line":332},[329,693,694],{"class":441},"\u002F\u002F O(N log M) where N = transactions, M = intervals\n",[329,696,697,700,703],{"class":331,"line":398},[329,698,699],{"class":339},"for each ",[329,701,702],{"class":404},"transaction",[329,704,705],{"class":343},":\n",[329,707,708],{"class":331,"line":460},[329,709,710],{"class":339},"  binary search intervals by date\n",[329,712,713],{"class":331,"line":556},[329,714,715],{"class":339},"  push into matching bucket\n",[197,717,718],{},"Intervals are sorted and non-overlapping, making binary search valid. Transactions outside any interval are silently skipped.",[201,720,722],{"id":721},"date-formatting","Date Formatting",[197,724,725,728,729,732,733,736],{},[230,726,727],{},"useGetDateRange()"," produces human-readable labels. Each format function returns the complete string directly (no ",[230,730,731],{},"start","\u002F",[230,734,735],{},"end"," halves to concatenate):",[320,738,741],{"className":739,"code":740,"language":601},[599],"duration=1, current period  -> \"Today\", \"This Month\", \"This Year\"\nduration=1, previous period -> \"Yesterday\", \"Last Month\", \"Last Year\"\nduration=1, same year       -> \"15 March\"\nduration>1, current period  -> \"Last 3 days\", \"Last 6 months\"\notherwise                   -> \"Mar - Jun\", \"10-15 Mar 2024\"\n",[230,742,740],{"__ignoreMap":325},[197,744,745,746,749],{},"Period matching uses ",[230,747,748],{},"isSamePeriod(date1, date2, period)"," - a single helper replacing repeated switch blocks.",[201,751,753],{"id":752},"query-param-parsing","Query Param Parsing",[197,755,756,759,760,762,763,766,767,770,771,774],{},[230,757,758],{},"parseStatDateQueryParams()"," merges URL query params into ",[230,761,265],{}," using Zod validation. All fields use ",[230,764,765],{},"!== undefined"," checks (not falsy) so that ",[230,768,769],{},"rangeOffset=0"," and ",[230,772,773],{},"intervalSelected=0"," are applied correctly.",[201,776,778],{"id":777},"average-computation","Average Computation",[197,780,781,784,785,788,789,791],{},[230,782,783],{},"computeAverageTotal()"," computes per-day\u002Fweek\u002Fmonth averages for a date range. Returns ",[230,786,787],{},"undefined"," for single-day ranges or zero sums (early exit before computing intervals). The ",[230,790,354],{}," average is always present when the function returns a result (range >= 2 days guaranteed), so no empty-object check is needed.",[201,793,795],{"id":794},"next-steps","Next Steps",[797,798,799,806],"ul",{},[800,801,802,805],"li",{},[803,804,134],"a",{"href":135}," - project structure and store pattern",[800,807,808,810],{},[803,809,153],{"href":154}," - rationale behind key design choices",[812,813,814],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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);}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":325,"searchDepth":398,"depth":398,"links":816},[817,818,821,822,825,826,827,828,829],{"id":203,"depth":398,"text":204},{"id":311,"depth":398,"text":312,"children":819},[820],{"id":483,"depth":460,"text":484},{"id":581,"depth":398,"text":582},{"id":628,"depth":398,"text":629,"children":823},[824],{"id":647,"depth":460,"text":167},{"id":677,"depth":398,"text":678},{"id":721,"depth":398,"text":722},{"id":752,"depth":398,"text":753},{"id":777,"depth":398,"text":778},{"id":794,"depth":398,"text":795},"Date range computation, period formatting, and interval bucketing.","md",null,{},{"icon":101},{"title":98,"description":836},"Date utilities in Finapp - type-safe period handling, interval computation, date range formatting, and transaction bucketing with binary search.","BbOsRw7nKywSXGiSU6-fNiFzqP__-kUb6ooT9DQk-4c",[839,841],{"title":93,"path":94,"stem":95,"description":840,"icon":96,"children":-1},"Unit and E2E testing scripts.",{"title":108,"path":109,"stem":110,"description":842,"icon":111,"children":-1},"How Claude Code is configured for the Finapp project, agents and skills.",1782114343824]