@mrck-labs/vanaheim-shared 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,29 +36,73 @@ __export(src_exports, {
36
36
  LINEAR_PRIORITY_COLORS: () => LINEAR_PRIORITY_COLORS,
37
37
  LINEAR_PRIORITY_LABELS: () => LINEAR_PRIORITY_LABELS,
38
38
  SETTING_KEYS: () => SETTING_KEYS,
39
+ activeSessionAtom: () => activeSessionAtom,
40
+ addDays: () => addDays,
39
41
  calculateFocusStats: () => calculateFocusStats,
40
42
  calculateLieuBalance: () => calculateLieuBalance,
41
43
  calculateMonthlyExpenses: () => calculateMonthlyExpenses,
42
44
  calculateMonthlyIncome: () => calculateMonthlyIncome,
43
45
  calculateMonthlySavings: () => calculateMonthlySavings,
44
46
  calculateSavingsRate: () => calculateSavingsRate,
47
+ elapsedSecondsAtom: () => elapsedSecondsAtom,
45
48
  formatCurrency: () => formatCurrency,
46
49
  formatDate: () => formatDate,
50
+ formatDateHeader: () => formatDateHeader,
51
+ formatDateLocalized: () => formatDateLocalized,
52
+ formatDateLong: () => formatDateLong,
53
+ formatDateString: () => formatDateString,
47
54
  formatDueDate: () => formatDueDate,
55
+ formatDueDateString: () => formatDueDateString,
56
+ formatDuration: () => formatDuration,
57
+ formatFullDate: () => formatFullDate,
58
+ formatMonthYear: () => formatMonthYear,
59
+ formatRelativeDueDate: () => formatRelativeDueDate,
60
+ formatRelativePayDate: () => formatRelativePayDate,
48
61
  formatRelativeTime: () => formatRelativeTime,
62
+ formatRelativeTimeExtended: () => formatRelativeTimeExtended,
49
63
  formatTime: () => formatTime,
64
+ formatTimeHHMM: () => formatTimeHHMM,
65
+ formatTimeLocalized: () => formatTimeLocalized,
66
+ formatTimeWithSeconds: () => formatTimeWithSeconds,
50
67
  formatTotalTime: () => formatTotalTime,
68
+ formatWeekRange: () => formatWeekRange,
69
+ formattedElapsedAtom: () => formattedElapsedAtom,
70
+ formattedRemainingAtom: () => formattedRemainingAtom,
51
71
  generateId: () => generateId,
52
72
  generateRandomColor: () => generateRandomColor,
53
73
  generateShortId: () => generateShortId,
74
+ getEndOfDayISO: () => getEndOfDayISO,
75
+ getNextWeek: () => getNextWeek,
76
+ getPreviousWeek: () => getPreviousWeek,
54
77
  getRepoName: () => getRepoName,
78
+ getStartOfDayISO: () => getStartOfDayISO,
79
+ getTodayMidnight: () => getTodayMidnight,
80
+ getTodayString: () => getTodayString,
81
+ getTomorrowString: () => getTomorrowString,
82
+ getWeekEnd: () => getWeekEnd,
83
+ getWeekEndString: () => getWeekEndString,
84
+ getWeekStart: () => getWeekStart,
85
+ getWeekStartString: () => getWeekStartString,
86
+ getYesterdayString: () => getYesterdayString,
87
+ isDueSoon: () => isDueSoon,
55
88
  isNonEmptyString: () => isNonEmptyString,
89
+ isOverdue: () => isOverdue,
56
90
  isPositiveNumber: () => isPositiveNumber,
91
+ isToday: () => isToday,
57
92
  isValidCurrency: () => isValidCurrency,
58
93
  isValidEmail: () => isValidEmail,
59
94
  isValidFrequency: () => isValidFrequency,
60
95
  isValidISODate: () => isValidISODate,
61
96
  isValidUrl: () => isValidUrl,
97
+ normalizeToMidnight: () => normalizeToMidnight,
98
+ parseLocalDate: () => parseLocalDate,
99
+ progressAtom: () => progressAtom,
100
+ progressPercentAtom: () => progressPercentAtom,
101
+ queryKeys: () => queryKeys,
102
+ remainingSecondsAtom: () => remainingSecondsAtom,
103
+ subtractDays: () => subtractDays,
104
+ targetSecondsAtom: () => targetSecondsAtom,
105
+ timerStatusAtom: () => timerStatusAtom,
62
106
  toMonthlyAmount: () => toMonthlyAmount,
63
107
  toYearlyAmount: () => toYearlyAmount,
64
108
  truncate: () => truncate
@@ -179,7 +223,19 @@ function formatTotalTime(seconds) {
179
223
  }
180
224
  return `${mins}m`;
181
225
  }
182
- function formatCurrency(amount, currency, locale = "en-CH") {
226
+ function formatDuration(seconds) {
227
+ const hours = Math.floor(seconds / 3600);
228
+ const minutes = Math.floor(seconds % 3600 / 60);
229
+ const secs = seconds % 60;
230
+ if (hours > 0) {
231
+ return `${hours}h ${minutes}m`;
232
+ } else if (minutes > 0) {
233
+ return `${minutes}m${secs > 0 ? ` ${secs}s` : ""}`;
234
+ } else {
235
+ return `${secs}s`;
236
+ }
237
+ }
238
+ function formatCurrency(amount, currency = "CHF", locale = "de-CH") {
183
239
  return new Intl.NumberFormat(locale, {
184
240
  style: "currency",
185
241
  currency,
@@ -216,12 +272,12 @@ function formatDueDate(dueDate) {
216
272
  tomorrow.setDate(tomorrow.getDate() + 1);
217
273
  const dueDay = new Date(due);
218
274
  dueDay.setHours(0, 0, 0, 0);
219
- const isOverdue = dueDay < today;
275
+ const isOverdue2 = dueDay < today;
220
276
  if (dueDay.getTime() === today.getTime()) {
221
277
  return { text: "Today", isOverdue: false };
222
278
  } else if (dueDay.getTime() === tomorrow.getTime()) {
223
279
  return { text: "Tomorrow", isOverdue: false };
224
- } else if (isOverdue) {
280
+ } else if (isOverdue2) {
225
281
  const daysAgo = Math.ceil(
226
282
  (today.getTime() - dueDay.getTime()) / (1e3 * 60 * 60 * 24)
227
283
  );
@@ -363,6 +419,517 @@ function calculateFocusStats(sessions) {
363
419
  completionRate
364
420
  };
365
421
  }
422
+
423
+ // src/query/index.ts
424
+ var queryKeys = {
425
+ // -------------------------------------------------------------------------
426
+ // Expenses
427
+ // -------------------------------------------------------------------------
428
+ expenses: {
429
+ all: ["expenses"],
430
+ lists: () => [...queryKeys.expenses.all, "list"],
431
+ list: (filters) => [...queryKeys.expenses.lists(), filters],
432
+ detail: (id) => [...queryKeys.expenses.all, "detail", id]
433
+ },
434
+ // Expense Categories
435
+ expenseCategories: {
436
+ all: ["expenseCategories"],
437
+ list: () => [...queryKeys.expenseCategories.all, "list"]
438
+ },
439
+ // Expense Payments
440
+ expensePayments: {
441
+ all: ["expensePayments"],
442
+ lists: () => [...queryKeys.expensePayments.all, "list"],
443
+ list: (filters) => [...queryKeys.expensePayments.lists(), filters],
444
+ detail: (id) => [...queryKeys.expensePayments.all, "detail", id]
445
+ },
446
+ // -------------------------------------------------------------------------
447
+ // Income
448
+ // -------------------------------------------------------------------------
449
+ incomes: {
450
+ all: ["incomes"],
451
+ lists: () => [...queryKeys.incomes.all, "list"],
452
+ list: (filters) => [...queryKeys.incomes.lists(), filters],
453
+ detail: (id) => [...queryKeys.incomes.all, "detail", id]
454
+ },
455
+ // Income Categories
456
+ incomeCategories: {
457
+ all: ["incomeCategories"],
458
+ list: () => [...queryKeys.incomeCategories.all, "list"]
459
+ },
460
+ // Income Payments
461
+ incomePayments: {
462
+ all: ["incomePayments"],
463
+ lists: () => [...queryKeys.incomePayments.all, "list"],
464
+ list: (filters) => [...queryKeys.incomePayments.lists(), filters],
465
+ detail: (id) => [...queryKeys.incomePayments.all, "detail", id]
466
+ },
467
+ // Exchange Rates
468
+ exchangeRates: {
469
+ all: ["exchangeRates"],
470
+ list: () => [...queryKeys.exchangeRates.all, "list"]
471
+ },
472
+ // -------------------------------------------------------------------------
473
+ // Focus
474
+ // -------------------------------------------------------------------------
475
+ focusSessions: {
476
+ all: ["focusSessions"],
477
+ lists: () => [...queryKeys.focusSessions.all, "list"],
478
+ list: (filters) => [...queryKeys.focusSessions.lists(), filters],
479
+ today: () => [...queryKeys.focusSessions.all, "today"],
480
+ active: () => [...queryKeys.focusSessions.all, "active"],
481
+ detail: (id) => [...queryKeys.focusSessions.all, "detail", id]
482
+ },
483
+ // Focus Categories
484
+ focusCategories: {
485
+ all: ["focusCategories"],
486
+ list: () => [...queryKeys.focusCategories.all, "list"]
487
+ },
488
+ // -------------------------------------------------------------------------
489
+ // Settings
490
+ // -------------------------------------------------------------------------
491
+ settings: {
492
+ all: ["settings"],
493
+ detail: (key) => [...queryKeys.settings.all, key]
494
+ },
495
+ // -------------------------------------------------------------------------
496
+ // EF Work (Lieu Days & Links)
497
+ // -------------------------------------------------------------------------
498
+ lieuDays: {
499
+ all: ["lieuDays"],
500
+ list: () => [...queryKeys.lieuDays.all, "list"],
501
+ balance: () => [...queryKeys.lieuDays.all, "balance"],
502
+ detail: (id) => [...queryKeys.lieuDays.all, "detail", id]
503
+ },
504
+ efLinks: {
505
+ all: ["efLinks"],
506
+ list: () => [...queryKeys.efLinks.all, "list"],
507
+ detail: (id) => [...queryKeys.efLinks.all, "detail", id]
508
+ },
509
+ // -------------------------------------------------------------------------
510
+ // Banking
511
+ // -------------------------------------------------------------------------
512
+ bankConnections: {
513
+ all: ["bankConnections"],
514
+ list: () => [...queryKeys.bankConnections.all, "list"],
515
+ detail: (id) => [...queryKeys.bankConnections.all, "detail", id]
516
+ },
517
+ bankTransactions: {
518
+ all: ["bankTransactions"],
519
+ lists: () => [...queryKeys.bankTransactions.all, "list"],
520
+ list: (filters) => [...queryKeys.bankTransactions.lists(), filters],
521
+ stats: (connectionId) => [...queryKeys.bankTransactions.all, "stats", connectionId]
522
+ },
523
+ // -------------------------------------------------------------------------
524
+ // Health
525
+ // -------------------------------------------------------------------------
526
+ healthCategories: {
527
+ all: ["healthCategories"],
528
+ list: () => [...queryKeys.healthCategories.all, "list"],
529
+ detail: (id) => [...queryKeys.healthCategories.all, "detail", id]
530
+ },
531
+ healthHabits: {
532
+ all: ["healthHabits"],
533
+ lists: () => [...queryKeys.healthHabits.all, "list"],
534
+ list: (filters) => [...queryKeys.healthHabits.lists(), filters],
535
+ detail: (id) => [...queryKeys.healthHabits.all, "detail", id]
536
+ },
537
+ healthCompletions: {
538
+ all: ["healthCompletions"],
539
+ lists: () => [...queryKeys.healthCompletions.all, "list"],
540
+ list: (filters) => [...queryKeys.healthCompletions.lists(), filters],
541
+ forHabit: (habitId) => [...queryKeys.healthCompletions.all, "habit", habitId],
542
+ forDate: (date) => [...queryKeys.healthCompletions.all, "date", date]
543
+ },
544
+ healthStats: {
545
+ all: ["healthStats"],
546
+ forDate: (date) => [...queryKeys.healthStats.all, date ?? "today"]
547
+ },
548
+ // -------------------------------------------------------------------------
549
+ // Prompts
550
+ // -------------------------------------------------------------------------
551
+ promptCategories: {
552
+ all: ["promptCategories"],
553
+ list: () => [...queryKeys.promptCategories.all, "list"],
554
+ detail: (id) => [...queryKeys.promptCategories.all, "detail", id]
555
+ },
556
+ promptLabels: {
557
+ all: ["promptLabels"],
558
+ list: () => [...queryKeys.promptLabels.all, "list"],
559
+ detail: (id) => [...queryKeys.promptLabels.all, "detail", id]
560
+ },
561
+ prompts: {
562
+ all: ["prompts"],
563
+ lists: () => [...queryKeys.prompts.all, "list"],
564
+ list: (filters) => [...queryKeys.prompts.lists(), filters],
565
+ detail: (id) => [...queryKeys.prompts.all, "detail", id]
566
+ },
567
+ // -------------------------------------------------------------------------
568
+ // Training
569
+ // -------------------------------------------------------------------------
570
+ trainingActivities: {
571
+ all: ["trainingActivities"],
572
+ list: () => [...queryKeys.trainingActivities.all, "list"],
573
+ detail: (id) => [...queryKeys.trainingActivities.all, "detail", id],
574
+ byStravaType: (stravaType) => [...queryKeys.trainingActivities.all, "strava", stravaType]
575
+ },
576
+ trainingSessions: {
577
+ all: ["trainingSessions"],
578
+ lists: () => [...queryKeys.trainingSessions.all, "list"],
579
+ list: (filters) => [...queryKeys.trainingSessions.lists(), filters],
580
+ detail: (id) => [...queryKeys.trainingSessions.all, "detail", id],
581
+ byStravaId: (stravaActivityId) => [...queryKeys.trainingSessions.all, "strava", stravaActivityId]
582
+ },
583
+ plannedSessions: {
584
+ all: ["plannedSessions"],
585
+ lists: () => [...queryKeys.plannedSessions.all, "list"],
586
+ list: (filters) => [...queryKeys.plannedSessions.lists(), filters],
587
+ detail: (id) => [...queryKeys.plannedSessions.all, "detail", id],
588
+ upcoming: () => [...queryKeys.plannedSessions.all, "upcoming"]
589
+ },
590
+ races: {
591
+ all: ["races"],
592
+ lists: () => [...queryKeys.races.all, "list"],
593
+ list: (filters) => [...queryKeys.races.lists(), filters],
594
+ detail: (id) => [...queryKeys.races.all, "detail", id],
595
+ next: () => [...queryKeys.races.all, "next"]
596
+ },
597
+ trainingStats: {
598
+ all: ["trainingStats"],
599
+ forRange: (dateFrom, dateTo) => [...queryKeys.trainingStats.all, dateFrom, dateTo]
600
+ },
601
+ // Strava
602
+ strava: {
603
+ all: ["strava"],
604
+ athlete: () => [...queryKeys.strava.all, "athlete"],
605
+ connected: () => [...queryKeys.strava.all, "connected"],
606
+ activities: (options) => [...queryKeys.strava.all, "activities", options]
607
+ },
608
+ // -------------------------------------------------------------------------
609
+ // Journal
610
+ // -------------------------------------------------------------------------
611
+ journalEntries: {
612
+ all: ["journalEntries"],
613
+ lists: () => [...queryKeys.journalEntries.all, "list"],
614
+ list: (filters) => [...queryKeys.journalEntries.lists(), filters],
615
+ forDate: (date) => [...queryKeys.journalEntries.all, "date", date]
616
+ },
617
+ // Journal data (aggregated)
618
+ journalData: {
619
+ all: ["journalData"],
620
+ forDate: (date) => [...queryKeys.journalData.all, date]
621
+ },
622
+ // -------------------------------------------------------------------------
623
+ // Chat
624
+ // -------------------------------------------------------------------------
625
+ chatConversations: {
626
+ all: ["chatConversations"],
627
+ list: () => [...queryKeys.chatConversations.all, "list"],
628
+ detail: (id) => [...queryKeys.chatConversations.all, "detail", id]
629
+ },
630
+ chatMessages: {
631
+ all: ["chatMessages"],
632
+ forConversation: (conversationId) => [...queryKeys.chatMessages.all, "conversation", conversationId]
633
+ },
634
+ // -------------------------------------------------------------------------
635
+ // Google Calendar
636
+ // -------------------------------------------------------------------------
637
+ googleCalendar: {
638
+ all: ["googleCalendar"],
639
+ calendars: () => [...queryKeys.googleCalendar.all, "calendars"],
640
+ events: (calendarId, timeMin, timeMax) => [...queryKeys.googleCalendar.all, "events", calendarId, timeMin, timeMax]
641
+ },
642
+ // -------------------------------------------------------------------------
643
+ // Linear
644
+ // -------------------------------------------------------------------------
645
+ linear: {
646
+ all: ["linear"],
647
+ issues: (filters) => [...queryKeys.linear.all, "issues", filters],
648
+ projects: () => [...queryKeys.linear.all, "projects"]
649
+ }
650
+ };
651
+
652
+ // src/atoms/focus.ts
653
+ var import_jotai = require("jotai");
654
+ var timerStatusAtom = (0, import_jotai.atom)("idle");
655
+ var targetSecondsAtom = (0, import_jotai.atom)(25 * 60);
656
+ var elapsedSecondsAtom = (0, import_jotai.atom)(0);
657
+ var activeSessionAtom = (0, import_jotai.atom)(null);
658
+ var remainingSecondsAtom = (0, import_jotai.atom)((get) => {
659
+ const target = get(targetSecondsAtom);
660
+ const elapsed = get(elapsedSecondsAtom);
661
+ return Math.max(0, target - elapsed);
662
+ });
663
+ var progressAtom = (0, import_jotai.atom)((get) => {
664
+ const target = get(targetSecondsAtom);
665
+ const elapsed = get(elapsedSecondsAtom);
666
+ if (target === 0) return 0;
667
+ return Math.min(1, elapsed / target);
668
+ });
669
+ var formattedRemainingAtom = (0, import_jotai.atom)((get) => {
670
+ return formatTime(get(remainingSecondsAtom));
671
+ });
672
+ var formattedElapsedAtom = (0, import_jotai.atom)((get) => {
673
+ return formatTime(get(elapsedSecondsAtom));
674
+ });
675
+ var progressPercentAtom = (0, import_jotai.atom)((get) => {
676
+ return get(progressAtom) * 100;
677
+ });
678
+
679
+ // src/date/index.ts
680
+ function normalizeToMidnight(date) {
681
+ const d = new Date(date);
682
+ d.setHours(0, 0, 0, 0);
683
+ return d;
684
+ }
685
+ function getTodayMidnight() {
686
+ return normalizeToMidnight(/* @__PURE__ */ new Date());
687
+ }
688
+ function formatDateString(date) {
689
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
690
+ }
691
+ function getTodayString() {
692
+ return formatDateString(/* @__PURE__ */ new Date());
693
+ }
694
+ function parseLocalDate(dateStr) {
695
+ return /* @__PURE__ */ new Date(dateStr + "T00:00:00");
696
+ }
697
+ function isToday(dateStr) {
698
+ if (!dateStr) return false;
699
+ return dateStr === getTodayString();
700
+ }
701
+ function isOverdue(dateStr) {
702
+ if (!dateStr) return false;
703
+ return dateStr < getTodayString();
704
+ }
705
+ function isDueSoon(dateStr, daysThreshold = 7) {
706
+ if (!dateStr) return false;
707
+ if (isOverdue(dateStr)) return false;
708
+ const today = getTodayMidnight();
709
+ const targetDate = parseLocalDate(dateStr);
710
+ const diffMs = targetDate.getTime() - today.getTime();
711
+ const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
712
+ return diffDays >= 0 && diffDays <= daysThreshold;
713
+ }
714
+ function addDays(dateStr, days) {
715
+ const date = parseLocalDate(dateStr);
716
+ date.setDate(date.getDate() + days);
717
+ return formatDateString(date);
718
+ }
719
+ function subtractDays(dateStr, days) {
720
+ return addDays(dateStr, -days);
721
+ }
722
+ function getYesterdayString() {
723
+ return subtractDays(getTodayString(), 1);
724
+ }
725
+ function getTomorrowString() {
726
+ return addDays(getTodayString(), 1);
727
+ }
728
+ function getStartOfDayISO(dateStr) {
729
+ const date = parseLocalDate(dateStr);
730
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString();
731
+ }
732
+ function getEndOfDayISO(dateStr) {
733
+ const date = parseLocalDate(dateStr);
734
+ return new Date(
735
+ date.getFullYear(),
736
+ date.getMonth(),
737
+ date.getDate(),
738
+ 23,
739
+ 59,
740
+ 59,
741
+ 999
742
+ ).toISOString();
743
+ }
744
+ function getWeekStart(date) {
745
+ const d = new Date(date);
746
+ const day = d.getDay();
747
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1);
748
+ d.setDate(diff);
749
+ d.setHours(0, 0, 0, 0);
750
+ return d;
751
+ }
752
+ function getWeekEnd(weekStart) {
753
+ const d = new Date(weekStart);
754
+ d.setDate(d.getDate() + 6);
755
+ return d;
756
+ }
757
+ function getPreviousWeek(weekStart) {
758
+ const d = new Date(weekStart);
759
+ d.setDate(d.getDate() - 7);
760
+ return d;
761
+ }
762
+ function getNextWeek(weekStart) {
763
+ const d = new Date(weekStart);
764
+ d.setDate(d.getDate() + 7);
765
+ return d;
766
+ }
767
+ function getWeekStartString(baseDate = /* @__PURE__ */ new Date()) {
768
+ return formatDateString(getWeekStart(baseDate));
769
+ }
770
+ function getWeekEndString(baseDate = /* @__PURE__ */ new Date()) {
771
+ return formatDateString(getWeekEnd(getWeekStart(baseDate)));
772
+ }
773
+ function formatWeekRange(weekStart) {
774
+ const weekEnd = getWeekEnd(weekStart);
775
+ const startMonth = weekStart.toLocaleDateString("en-US", { month: "short" });
776
+ const endMonth = weekEnd.toLocaleDateString("en-US", { month: "short" });
777
+ if (startMonth === endMonth) {
778
+ return `${startMonth} ${weekStart.getDate()} - ${weekEnd.getDate()}, ${weekStart.getFullYear()}`;
779
+ }
780
+ return `${startMonth} ${weekStart.getDate()} - ${endMonth} ${weekEnd.getDate()}, ${weekStart.getFullYear()}`;
781
+ }
782
+ function formatFullDate(dateStr) {
783
+ const date = parseLocalDate(dateStr);
784
+ return date.toLocaleDateString("en-US", {
785
+ weekday: "long",
786
+ year: "numeric",
787
+ month: "long",
788
+ day: "numeric"
789
+ });
790
+ }
791
+ function formatDateLong(dateStr) {
792
+ const date = parseLocalDate(dateStr);
793
+ return date.toLocaleDateString("en-GB", {
794
+ weekday: "long",
795
+ day: "numeric",
796
+ month: "long"
797
+ });
798
+ }
799
+ function formatMonthYear(date) {
800
+ return date.toLocaleDateString("en-US", {
801
+ month: "long",
802
+ year: "numeric"
803
+ });
804
+ }
805
+ function formatTimeHHMM(date) {
806
+ const d = typeof date === "string" ? new Date(date) : date;
807
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
808
+ }
809
+ function formatTimeWithSeconds(date) {
810
+ const d = typeof date === "string" ? new Date(date) : date;
811
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
812
+ }
813
+ function formatDateHeader(dateStr) {
814
+ const today = getTodayString();
815
+ if (dateStr < today) {
816
+ return "Overdue";
817
+ }
818
+ if (dateStr === today) {
819
+ return "Today";
820
+ }
821
+ const tomorrowStr = getTomorrowString();
822
+ if (dateStr === tomorrowStr) {
823
+ return "Tomorrow";
824
+ }
825
+ const date = parseLocalDate(dateStr);
826
+ return date.toLocaleDateString("en-US", {
827
+ weekday: "long",
828
+ month: "short",
829
+ day: "numeric"
830
+ });
831
+ }
832
+ function formatDueDateString(dateStr) {
833
+ if (!dateStr) return "No due date";
834
+ const today = getTodayString();
835
+ const tomorrowStr = getTomorrowString();
836
+ if (dateStr < today) {
837
+ const date2 = parseLocalDate(dateStr);
838
+ return `Overdue \u2022 ${date2.toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;
839
+ }
840
+ if (dateStr === today) {
841
+ return "Today";
842
+ }
843
+ if (dateStr === tomorrowStr) {
844
+ return "Tomorrow";
845
+ }
846
+ const date = parseLocalDate(dateStr);
847
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
848
+ const dateYear = date.getFullYear();
849
+ return date.toLocaleDateString("en-US", {
850
+ month: "short",
851
+ day: "numeric",
852
+ year: dateYear !== currentYear ? "numeric" : void 0
853
+ });
854
+ }
855
+ function formatRelativeTimeExtended(dateStr) {
856
+ const date = typeof dateStr === "string" ? new Date(dateStr) : dateStr;
857
+ const now = /* @__PURE__ */ new Date();
858
+ const diffMs = now.getTime() - date.getTime();
859
+ const diffMins = Math.floor(diffMs / 6e4);
860
+ const diffHours = Math.floor(diffMins / 60);
861
+ const diffDays = Math.floor(diffHours / 24);
862
+ if (diffMins < 1) return "Just now";
863
+ if (diffMins < 60) return `${diffMins}m ago`;
864
+ if (diffHours < 24) return `${diffHours}h ago`;
865
+ if (diffDays < 7) return `${diffDays}d ago`;
866
+ return date.toLocaleDateString();
867
+ }
868
+ function formatRelativeDueDate(dateStr, options) {
869
+ const { duePrefix = "Due", overduePrefix = "" } = options || {};
870
+ const date = parseLocalDate(dateStr);
871
+ const now = normalizeToMidnight(/* @__PURE__ */ new Date());
872
+ const diffMs = date.getTime() - now.getTime();
873
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
874
+ if (diffDays < 0) {
875
+ const absDays = Math.abs(diffDays);
876
+ return overduePrefix ? `${overduePrefix} ${absDays} day${absDays !== 1 ? "s" : ""} overdue` : `${absDays} day${absDays !== 1 ? "s" : ""} overdue`;
877
+ }
878
+ if (diffDays === 0) return `${duePrefix} today`;
879
+ if (diffDays === 1) return `${duePrefix} tomorrow`;
880
+ if (diffDays <= 7) return `${duePrefix} in ${diffDays} days`;
881
+ return date.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
882
+ }
883
+ function formatRelativePayDate(dateStr) {
884
+ const date = parseLocalDate(dateStr);
885
+ const now = normalizeToMidnight(/* @__PURE__ */ new Date());
886
+ const diffMs = date.getTime() - now.getTime();
887
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
888
+ if (diffDays < 0) {
889
+ const absDays = Math.abs(diffDays);
890
+ return `${absDays} day${absDays !== 1 ? "s" : ""} ago`;
891
+ }
892
+ if (diffDays === 0) return "Today";
893
+ if (diffDays === 1) return "Tomorrow";
894
+ if (diffDays <= 7) return `In ${diffDays} days`;
895
+ return date.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
896
+ }
897
+ function formatDateLocalized(date, format = "medium") {
898
+ const d = typeof date === "string" ? new Date(date) : date;
899
+ switch (format) {
900
+ case "short":
901
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
902
+ case "medium":
903
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
904
+ case "long":
905
+ return d.toLocaleDateString("en-US", {
906
+ weekday: "long",
907
+ month: "long",
908
+ day: "numeric",
909
+ year: "numeric"
910
+ });
911
+ case "weekday":
912
+ return d.toLocaleDateString("en-US", { weekday: "short" });
913
+ default:
914
+ return d.toLocaleDateString();
915
+ }
916
+ }
917
+ function formatTimeLocalized(date, format = "short") {
918
+ const d = typeof date === "string" ? new Date(date) : date;
919
+ switch (format) {
920
+ case "short":
921
+ return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: true });
922
+ case "withSeconds":
923
+ return d.toLocaleTimeString("en-US", {
924
+ hour: "2-digit",
925
+ minute: "2-digit",
926
+ second: "2-digit",
927
+ hour12: true
928
+ });
929
+ default:
930
+ return d.toLocaleTimeString();
931
+ }
932
+ }
366
933
  // Annotate the CommonJS export names for ESM import in node:
367
934
  0 && (module.exports = {
368
935
  API_URLS,
@@ -381,29 +948,73 @@ function calculateFocusStats(sessions) {
381
948
  LINEAR_PRIORITY_COLORS,
382
949
  LINEAR_PRIORITY_LABELS,
383
950
  SETTING_KEYS,
951
+ activeSessionAtom,
952
+ addDays,
384
953
  calculateFocusStats,
385
954
  calculateLieuBalance,
386
955
  calculateMonthlyExpenses,
387
956
  calculateMonthlyIncome,
388
957
  calculateMonthlySavings,
389
958
  calculateSavingsRate,
959
+ elapsedSecondsAtom,
390
960
  formatCurrency,
391
961
  formatDate,
962
+ formatDateHeader,
963
+ formatDateLocalized,
964
+ formatDateLong,
965
+ formatDateString,
392
966
  formatDueDate,
967
+ formatDueDateString,
968
+ formatDuration,
969
+ formatFullDate,
970
+ formatMonthYear,
971
+ formatRelativeDueDate,
972
+ formatRelativePayDate,
393
973
  formatRelativeTime,
974
+ formatRelativeTimeExtended,
394
975
  formatTime,
976
+ formatTimeHHMM,
977
+ formatTimeLocalized,
978
+ formatTimeWithSeconds,
395
979
  formatTotalTime,
980
+ formatWeekRange,
981
+ formattedElapsedAtom,
982
+ formattedRemainingAtom,
396
983
  generateId,
397
984
  generateRandomColor,
398
985
  generateShortId,
986
+ getEndOfDayISO,
987
+ getNextWeek,
988
+ getPreviousWeek,
399
989
  getRepoName,
990
+ getStartOfDayISO,
991
+ getTodayMidnight,
992
+ getTodayString,
993
+ getTomorrowString,
994
+ getWeekEnd,
995
+ getWeekEndString,
996
+ getWeekStart,
997
+ getWeekStartString,
998
+ getYesterdayString,
999
+ isDueSoon,
400
1000
  isNonEmptyString,
1001
+ isOverdue,
401
1002
  isPositiveNumber,
1003
+ isToday,
402
1004
  isValidCurrency,
403
1005
  isValidEmail,
404
1006
  isValidFrequency,
405
1007
  isValidISODate,
406
1008
  isValidUrl,
1009
+ normalizeToMidnight,
1010
+ parseLocalDate,
1011
+ progressAtom,
1012
+ progressPercentAtom,
1013
+ queryKeys,
1014
+ remainingSecondsAtom,
1015
+ subtractDays,
1016
+ targetSecondsAtom,
1017
+ timerStatusAtom,
407
1018
  toMonthlyAmount,
408
1019
  toYearlyAmount,
409
1020
  truncate