@chatpanel/events 0.50.0 → 0.52.0

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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. package/schedule.js +45 -0
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@chatpanel/events",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "The canonical ChatPanel event-log and capability contracts — typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {
8
8
  ".": "./index.js",
9
9
  "./adapters.js": "./adapters.js",
10
+ "./attribution.js": "./attribution.js",
10
11
  "./backup-envelope.js": "./backup-envelope.js",
11
12
  "./capability.js": "./capability.js",
12
13
  "./citations.js": "./citations.js",
package/schedule.js CHANGED
@@ -112,6 +112,51 @@ export function nextFireAt(schedule, from) {
112
112
  }
113
113
  }
114
114
 
115
+ /** Sunday-first, matching `Date#getDay()` and the `weekday` field. */
116
+ export const WEEKDAY_NAMES = Object.freeze([
117
+ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
118
+ ]);
119
+
120
+ const hhmm = (s) => `${String(s?.hour ?? 0).padStart(2, '0')}:${String(s?.minute ?? 0).padStart(2, '0')}`;
121
+
122
+ /**
123
+ * A schedule as a sentence: "every weekday at 08:00", not `{kind:'daily',hour:8,...}`.
124
+ *
125
+ * It lives beside the model rather than in a client because a schedule the user cannot read
126
+ * back is a schedule they cannot trust, and every client has to solve that. The desktop and
127
+ * the extension had already written this twice, and the copies disagreed: one rendered
128
+ * `weekdaysOnly` and the other silently dropped it, so a job that skipped weekends still
129
+ * read as "every day".
130
+ *
131
+ * Returns '' for a schedule it cannot describe rather than inventing one — an unreadable
132
+ * label is better than a confident wrong one.
133
+ */
134
+ export function describeSchedule(s) {
135
+ if (!s || typeof s !== 'object') return '';
136
+ switch (s.kind) {
137
+ case 'once': {
138
+ if (!(s.at > 0)) return '';
139
+ return `once, at ${new Date(s.at).toLocaleString()}`;
140
+ }
141
+ case 'interval': {
142
+ const ms = Number(s.everyMs) || 0;
143
+ if (ms < 60_000) return '';
144
+ const mins = Math.round(ms / 60_000);
145
+ if (mins % 1440 === 0) { const d = mins / 1440; return `every ${d === 1 ? 'day' : `${d} days`}`; }
146
+ if (mins % 60 === 0) { const h = mins / 60; return `every ${h === 1 ? 'hour' : `${h} hours`}`; }
147
+ return `every ${mins} minutes`;
148
+ }
149
+ case 'daily':
150
+ return s.weekdaysOnly ? `every weekday at ${hhmm(s)}` : `every day at ${hhmm(s)}`;
151
+ case 'weekly': {
152
+ const name = WEEKDAY_NAMES[s.weekday];
153
+ return name ? `every ${name} at ${hhmm(s)}` : '';
154
+ }
155
+ default:
156
+ return '';
157
+ }
158
+ }
159
+
115
160
  /** Every firing in (from, to], oldest first. Capped: a long sleep is not a queue of work. */
116
161
  export function occurrencesBetween(schedule, from, to, max = MAX_CATCH_UP) {
117
162
  const out = [];