daily-focus 1.1.1-dev.3 → 1.2.0-dev.11
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/README.md +39 -23
- package/SETUP.md +153 -38
- package/dist/{audit-B5GKGH2P.js → audit-F26VGPOO.js} +6 -5
- package/dist/{chunk-NWYOCDUU.js → chunk-4DZEBUPU.js} +9 -7
- package/dist/{chunk-C6FXWHGT.js → chunk-CPJ6LOVC.js} +123 -282
- package/dist/chunk-EV44BC2F.js +1020 -0
- package/dist/{chunk-AUBJ2IDQ.js → chunk-GVLNDQVQ.js} +2 -2
- package/dist/{chunk-YWHADSKR.js → chunk-HVCCIZGA.js} +1 -1
- package/dist/cli.js +9 -8
- package/dist/{demo-AMK3Y4JZ.js → demo-UMGG3VKY.js} +135 -32
- package/dist/{init-YGOPPELJ.js → init-MFPXASX3.js} +1 -9
- package/dist/{seed-57RJGU4Y.js → seed-S32JX5MW.js} +3 -3
- package/dist/{server-5HCA62YY.js → server-EDPYEZCP.js} +2088 -564
- package/dist/{service-63U4WBUH.js → service-XPFMYIHR.js} +1 -1
- package/package.json +2 -2
- package/prompts/README.md +8 -7
- package/prompts/assistant.md +14 -3
- package/prompts/morning-brief-personal.md +1 -25
- package/prompts/morning-brief-work.md +8 -33
- package/prompts/onboarding.md +41 -0
- package/public/app.js +1084 -747
- package/public/style.css +770 -261
- package/dist/chunk-GFCWPYEI.js +0 -456
package/public/app.js
CHANGED
|
@@ -69,9 +69,9 @@ function H() {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
function L(n3, l5, u4, t4, i4, r4, o4, e4, f4, c4, a4) {
|
|
72
|
-
var s4, h5, p5, v4,
|
|
73
|
-
for (f4 = T(u4, l5, g4, f4, m4), s4 = 0; s4 < m4; s4++) null != (p5 = u4.__k[s4]) && (h5 = -1 != p5.__i && g4[p5.__i] || d, p5.__i = s4, _5 = q(n3, p5, h5, i4, r4, o4, e4, f4, c4, a4), v4 = p5.__e, p5.ref && h5.ref != p5.ref && (h5.ref && J(h5.ref, null, p5), a4.push(p5.ref, p5.__c || v4, p5)), null ==
|
|
74
|
-
return u4.__e =
|
|
72
|
+
var s4, h5, p5, v4, y5, _5, g4 = t4 && t4.__k || w, m4 = l5.length;
|
|
73
|
+
for (f4 = T(u4, l5, g4, f4, m4), s4 = 0; s4 < m4; s4++) null != (p5 = u4.__k[s4]) && (h5 = -1 != p5.__i && g4[p5.__i] || d, p5.__i = s4, _5 = q(n3, p5, h5, i4, r4, o4, e4, f4, c4, a4), v4 = p5.__e, p5.ref && h5.ref != p5.ref && (h5.ref && J(h5.ref, null, p5), a4.push(p5.ref, p5.__c || v4, p5)), null == y5 && null != v4 && (y5 = v4), 4 & p5.__u ? (f4 = j(p5, f4, n3), h5.__e && (h5.__e = null)) : "function" == typeof p5.type && void 0 !== _5 ? f4 = _5 : v4 && (f4 = v4.nextSibling), p5.__u &= -7);
|
|
74
|
+
return u4.__e = y5, f4;
|
|
75
75
|
}
|
|
76
76
|
function T(n3, l5, u4, t4, i4) {
|
|
77
77
|
var r4, o4, e4, f4, c4, a4 = u4.length, s4 = a4, h5 = 0;
|
|
@@ -131,29 +131,29 @@ function V(n3) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
function q(n3, u4, t4, i4, r4, o4, e4, f4, c4, a4) {
|
|
134
|
-
var s4, h5, p5, v4,
|
|
134
|
+
var s4, h5, p5, v4, y5, d5, _5, k3, x4, M, I2, P2, A3, H2, T4, j4, F2 = u4.type;
|
|
135
135
|
if (void 0 !== u4.constructor) return null;
|
|
136
136
|
128 & t4.__u && (c4 = !!(32 & t4.__u), o4 = [f4 = u4.__e = t4.__e]), (s4 = l.__b) && s4(u4);
|
|
137
137
|
n: if ("function" == typeof F2) {
|
|
138
138
|
h5 = e4.length;
|
|
139
139
|
try {
|
|
140
|
-
if (x4 = u4.props, M = F2.prototype && F2.prototype.render, I2 = (s4 = F2.contextType) && i4[s4.__c], P2 = s4 ? I2 ? I2.props.value : s4.__ : i4, t4.__c ? k3 = (p5 = u4.__c = t4.__c).__ = p5.__E : (M ? u4.__c = p5 = new F2(x4, P2) : (u4.__c = p5 = new C(x4, P2), p5.constructor = F2, p5.render = Q), I2 && I2.sub(p5), p5.state || (p5.state = {}), p5.__n = i4, v4 = p5.__d = true, p5.__h = [], p5._sb = []), M && null == p5.__s && (p5.__s = p5.state), M && null != F2.getDerivedStateFromProps && (p5.__s == p5.state && (p5.__s = m({}, p5.__s)), m(p5.__s, F2.getDerivedStateFromProps(x4, p5.__s))),
|
|
140
|
+
if (x4 = u4.props, M = F2.prototype && F2.prototype.render, I2 = (s4 = F2.contextType) && i4[s4.__c], P2 = s4 ? I2 ? I2.props.value : s4.__ : i4, t4.__c ? k3 = (p5 = u4.__c = t4.__c).__ = p5.__E : (M ? u4.__c = p5 = new F2(x4, P2) : (u4.__c = p5 = new C(x4, P2), p5.constructor = F2, p5.render = Q), I2 && I2.sub(p5), p5.state || (p5.state = {}), p5.__n = i4, v4 = p5.__d = true, p5.__h = [], p5._sb = []), M && null == p5.__s && (p5.__s = p5.state), M && null != F2.getDerivedStateFromProps && (p5.__s == p5.state && (p5.__s = m({}, p5.__s)), m(p5.__s, F2.getDerivedStateFromProps(x4, p5.__s))), y5 = p5.props, d5 = p5.state, p5.__v = u4, v4) M && null == F2.getDerivedStateFromProps && null != p5.componentWillMount && p5.componentWillMount(), M && null != p5.componentDidMount && p5.__h.push(p5.componentDidMount);
|
|
141
141
|
else {
|
|
142
|
-
if (M && null == F2.getDerivedStateFromProps && x4 !==
|
|
142
|
+
if (M && null == F2.getDerivedStateFromProps && x4 !== y5 && null != p5.componentWillReceiveProps && p5.componentWillReceiveProps(x4, P2), u4.__v == t4.__v || !p5.__e && null != p5.shouldComponentUpdate && false === p5.shouldComponentUpdate(x4, p5.__s, P2)) {
|
|
143
143
|
u4.__v != t4.__v && (p5.props = x4, p5.state = p5.__s, p5.__d = false), u4.__e = t4.__e, u4.__k = t4.__k, u4.__k.some(function(n4) {
|
|
144
144
|
n4 && (n4.__ = u4);
|
|
145
145
|
}), w.push.apply(p5.__h, p5._sb), p5._sb = [], p5.__h.length && e4.push(p5), f4 = $(t4);
|
|
146
146
|
break n;
|
|
147
147
|
}
|
|
148
148
|
null != p5.componentWillUpdate && p5.componentWillUpdate(x4, p5.__s, P2), M && null != p5.componentDidUpdate && p5.__h.push(function() {
|
|
149
|
-
p5.componentDidUpdate(
|
|
149
|
+
p5.componentDidUpdate(y5, d5, _5);
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
if (p5.context = P2, p5.props = x4, p5.__P = n3, p5.__e = false, A3 = l.__r, H2 = 0, M) p5.state = p5.__s, p5.__d = false, A3 && A3(u4), s4 = p5.render(p5.props, p5.state, p5.context), w.push.apply(p5.__h, p5._sb), p5._sb = [];
|
|
153
153
|
else do {
|
|
154
154
|
p5.__d = false, A3 && A3(u4), s4 = p5.render(p5.props, p5.state, p5.context), p5.state = p5.__s;
|
|
155
155
|
} while (p5.__d && ++H2 < 25);
|
|
156
|
-
p5.state = p5.__s, null != p5.getChildContext && (i4 = m(m({}, i4), p5.getChildContext())), M && !v4 && null != p5.getSnapshotBeforeUpdate && (_5 = p5.getSnapshotBeforeUpdate(
|
|
156
|
+
p5.state = p5.__s, null != p5.getChildContext && (i4 = m(m({}, i4), p5.getChildContext())), M && !v4 && null != p5.getSnapshotBeforeUpdate && (_5 = p5.getSnapshotBeforeUpdate(y5, d5)), T4 = null != s4 && s4.type === S && null == s4.key ? E(s4.props.children) : s4, f4 = L(n3, g(T4) ? T4 : [T4], u4, t4, i4, r4, o4, e4, f4, c4, a4), p5.base = u4.__e, u4.__u &= -161, p5.__h.length && e4.push(p5), k3 && (p5.__E = p5.__ = null);
|
|
157
157
|
} catch (n4) {
|
|
158
158
|
if (e4.length = h5, u4.__v = null, c4 || null != o4) {
|
|
159
159
|
if (n4.then) {
|
|
@@ -185,10 +185,10 @@ function E(n3) {
|
|
|
185
185
|
return "object" != typeof n3 || null == n3 || n3.__b > 0 ? n3 : g(n3) ? n3.map(E) : void 0 !== n3.constructor ? null : m({}, n3);
|
|
186
186
|
}
|
|
187
187
|
function G(u4, t4, i4, r4, o4, e4, f4, c4, a4) {
|
|
188
|
-
var s4, h5, p5, v4,
|
|
188
|
+
var s4, h5, p5, v4, y5, w5, _5, m4 = i4.props || d, k3 = t4.props, x4 = t4.type;
|
|
189
189
|
if ("svg" == x4 ? o4 = "http://www.w3.org/2000/svg" : "math" == x4 ? o4 = "http://www.w3.org/1998/Math/MathML" : o4 || (o4 = "http://www.w3.org/1999/xhtml"), null != e4) {
|
|
190
|
-
for (s4 = 0; s4 < e4.length; s4++) if ((
|
|
191
|
-
u4 =
|
|
190
|
+
for (s4 = 0; s4 < e4.length; s4++) if ((y5 = e4[s4]) && "setAttribute" in y5 == !!x4 && (x4 ? y5.localName == x4 : 3 == y5.nodeType)) {
|
|
191
|
+
u4 = y5, e4[s4] = null;
|
|
192
192
|
break;
|
|
193
193
|
}
|
|
194
194
|
}
|
|
@@ -198,9 +198,9 @@ function G(u4, t4, i4, r4, o4, e4, f4, c4, a4) {
|
|
|
198
198
|
}
|
|
199
199
|
if (null == x4) m4 === k3 || c4 && u4.data == k3 || (u4.data = k3);
|
|
200
200
|
else {
|
|
201
|
-
if (e4 = "textarea" == x4 && null != k3.defaultValue ? null : e4 && n.call(u4.childNodes), !c4 && null != e4) for (m4 = {}, s4 = 0; s4 < u4.attributes.length; s4++) m4[(
|
|
202
|
-
for (s4 in m4)
|
|
203
|
-
for (s4 in k3)
|
|
201
|
+
if (e4 = "textarea" == x4 && null != k3.defaultValue ? null : e4 && n.call(u4.childNodes), !c4 && null != e4) for (m4 = {}, s4 = 0; s4 < u4.attributes.length; s4++) m4[(y5 = u4.attributes[s4]).name] = y5.value;
|
|
202
|
+
for (s4 in m4) y5 = m4[s4], "dangerouslySetInnerHTML" == s4 ? p5 = y5 : "children" == s4 || s4 in k3 || "value" == s4 && "defaultValue" in k3 || "checked" == s4 && "defaultChecked" in k3 || N(u4, s4, null, y5, o4);
|
|
203
|
+
for (s4 in k3) y5 = k3[s4], "children" == s4 ? v4 = y5 : "dangerouslySetInnerHTML" == s4 ? h5 = y5 : "value" == s4 ? w5 = y5 : "checked" == s4 ? _5 = y5 : c4 && "function" != typeof y5 || m4[s4] === y5 || N(u4, s4, y5, m4[s4], o4);
|
|
204
204
|
if (h5) c4 || p5 && (h5.__html == p5.__html || h5.__html == u4.innerHTML) || (u4.innerHTML = h5.__html), t4.__k = [];
|
|
205
205
|
else if (p5 && (u4.innerHTML = ""), L("template" == t4.type ? u4.content : u4, g(v4) ? v4 : [v4], t4, i4, r4, "foreignObject" == x4 ? "http://www.w3.org/1999/xhtml" : o4, e4, f4, e4 ? e4[0] : i4.__k && $(i4, 0), c4, a4), null != e4) for (s4 = e4.length; s4--; ) b(e4[s4]);
|
|
206
206
|
c4 && "textarea" != x4 || (s4 = "value", "progress" == x4 && null == w5 ? u4.removeAttribute("value") : null != w5 && (w5 !== u4[s4] || "progress" == x4 && !w5 || "option" == x4 && w5 != m4[s4]) && N(u4, s4, w5, m4[s4], o4), s4 = "checked", null != _5 && _5 != u4[s4] && N(u4, s4, _5, m4[s4], o4));
|
|
@@ -274,6 +274,42 @@ function s2(n3, t4) {
|
|
|
274
274
|
var u4 = r2.__H || (r2.__H = { __: [], __h: [] });
|
|
275
275
|
return n3 >= u4.__.length && u4.__.push({}), u4.__[n3];
|
|
276
276
|
}
|
|
277
|
+
function d2(n3) {
|
|
278
|
+
return o2 = 1, y2(D2, n3);
|
|
279
|
+
}
|
|
280
|
+
function y2(n3, u4, i4) {
|
|
281
|
+
var o4 = s2(t2++, 2);
|
|
282
|
+
if (o4.t = n3, !o4.__c && (o4.__ = [i4 ? i4(u4) : D2(void 0, u4), function(n4) {
|
|
283
|
+
var t4 = o4.__N ? o4.__N[0] : o4.__[0], r4 = o4.t(t4, n4);
|
|
284
|
+
t4 !== r4 && (o4.__N = [r4, o4.__[1]], o4.__c.setState({}));
|
|
285
|
+
}], o4.__c = r2, !r2.__f)) {
|
|
286
|
+
var f4 = function(n4, t4, r4) {
|
|
287
|
+
if (!o4.__c.__H) return true;
|
|
288
|
+
var u5 = false, i5 = o4.__c.props !== n4;
|
|
289
|
+
if (o4.__c.__H.__.some(function(n5) {
|
|
290
|
+
if (n5.__N) {
|
|
291
|
+
u5 = true;
|
|
292
|
+
var t5 = n5.__[0];
|
|
293
|
+
n5.__ = n5.__N, n5.__N = void 0, t5 !== n5.__[0] && (i5 = true);
|
|
294
|
+
}
|
|
295
|
+
}), c4) {
|
|
296
|
+
var f5 = c4.call(this, n4, t4, r4);
|
|
297
|
+
return u5 ? f5 || i5 : f5;
|
|
298
|
+
}
|
|
299
|
+
return !u5 || i5;
|
|
300
|
+
};
|
|
301
|
+
r2.__f = true;
|
|
302
|
+
var c4 = r2.shouldComponentUpdate, e4 = r2.componentWillUpdate;
|
|
303
|
+
r2.componentWillUpdate = function(n4, t4, r4) {
|
|
304
|
+
if (this.__e) {
|
|
305
|
+
var u5 = c4;
|
|
306
|
+
c4 = void 0, f4(n4, t4, r4), c4 = u5;
|
|
307
|
+
}
|
|
308
|
+
e4 && e4.call(this, n4, t4, r4);
|
|
309
|
+
}, r2.shouldComponentUpdate = f4;
|
|
310
|
+
}
|
|
311
|
+
return o4.__N || o4.__;
|
|
312
|
+
}
|
|
277
313
|
function h2(n3, u4) {
|
|
278
314
|
var i4 = s2(t2++, 3);
|
|
279
315
|
!c2.__s && C2(i4.__H, u4) && (i4.__ = n3, i4.u = u4, r2.__H.__h.push(i4));
|
|
@@ -360,6 +396,9 @@ function C2(n3, t4) {
|
|
|
360
396
|
return t5 !== n3[r4];
|
|
361
397
|
});
|
|
362
398
|
}
|
|
399
|
+
function D2(n3, t4) {
|
|
400
|
+
return "function" == typeof t4 ? t4(n3) : t4;
|
|
401
|
+
}
|
|
363
402
|
|
|
364
403
|
// node_modules/@preact/signals-core/dist/signals-core.module.js
|
|
365
404
|
var i3 = /* @__PURE__ */ Symbol.for("preact-signals");
|
|
@@ -430,7 +469,7 @@ var s3 = 0;
|
|
|
430
469
|
var u3 = 0;
|
|
431
470
|
var e3 = 0;
|
|
432
471
|
var c3 = void 0;
|
|
433
|
-
var
|
|
472
|
+
var d3 = 0;
|
|
434
473
|
function a3(i4) {
|
|
435
474
|
if (void 0 !== o3) {
|
|
436
475
|
var t4 = i4.n;
|
|
@@ -543,7 +582,7 @@ Object.defineProperty(l3.prototype, "value", { get: function() {
|
|
|
543
582
|
})(this);
|
|
544
583
|
this.v = i4;
|
|
545
584
|
this.i++;
|
|
546
|
-
|
|
585
|
+
d3++;
|
|
547
586
|
v3++;
|
|
548
587
|
try {
|
|
549
588
|
for (var n3 = this.t; void 0 !== n3; n3 = n3.x) n3.t.N();
|
|
@@ -552,7 +591,7 @@ Object.defineProperty(l3.prototype, "value", { get: function() {
|
|
|
552
591
|
}
|
|
553
592
|
}
|
|
554
593
|
} });
|
|
555
|
-
function
|
|
594
|
+
function y3(i4, t4) {
|
|
556
595
|
return new l3(i4, t4);
|
|
557
596
|
}
|
|
558
597
|
function w3(i4) {
|
|
@@ -590,7 +629,7 @@ function p3(i4, t4) {
|
|
|
590
629
|
l3.call(this, void 0, t4);
|
|
591
630
|
this.x = i4;
|
|
592
631
|
this.s = void 0;
|
|
593
|
-
this.g =
|
|
632
|
+
this.g = d3 - 1;
|
|
594
633
|
this.f = 4;
|
|
595
634
|
}
|
|
596
635
|
p3.prototype = new l3();
|
|
@@ -599,8 +638,8 @@ p3.prototype.h = function() {
|
|
|
599
638
|
if (1 & this.f) return false;
|
|
600
639
|
if (32 == (36 & this.f)) return true;
|
|
601
640
|
this.f &= -5;
|
|
602
|
-
if (this.g ===
|
|
603
|
-
this.g =
|
|
641
|
+
if (this.g === d3) return true;
|
|
642
|
+
this.g = d3;
|
|
604
643
|
this.f |= 1;
|
|
605
644
|
if (this.i > 0 && !w3(this)) {
|
|
606
645
|
this.f &= -2;
|
|
@@ -754,7 +793,7 @@ function j3(i4, t4) {
|
|
|
754
793
|
// node_modules/@preact/signals/dist/signals.module.js
|
|
755
794
|
var l4;
|
|
756
795
|
var h4;
|
|
757
|
-
var
|
|
796
|
+
var d4;
|
|
758
797
|
var p4 = "undefined" != typeof window && !!window.__PREACT_SIGNALS_DEVTOOLS__;
|
|
759
798
|
var _4 = [];
|
|
760
799
|
j3(function() {
|
|
@@ -765,14 +804,14 @@ function g3(i4, r4) {
|
|
|
765
804
|
});
|
|
766
805
|
}
|
|
767
806
|
function b3(i4) {
|
|
768
|
-
if (
|
|
769
|
-
var n3 =
|
|
770
|
-
|
|
807
|
+
if (d4) {
|
|
808
|
+
var n3 = d4;
|
|
809
|
+
d4 = void 0;
|
|
771
810
|
n3();
|
|
772
811
|
}
|
|
773
|
-
|
|
812
|
+
d4 = i4 && i4.S();
|
|
774
813
|
}
|
|
775
|
-
function
|
|
814
|
+
function y4(i4) {
|
|
776
815
|
var n3 = this, t4 = i4.data, f4 = useSignal(t4);
|
|
777
816
|
f4.name = "ReactiveDom";
|
|
778
817
|
f4.value = t4;
|
|
@@ -802,8 +841,8 @@ function y3(i4) {
|
|
|
802
841
|
}, []), a4 = e4[0], v4 = e4[1];
|
|
803
842
|
return a4.value ? v4.peek() : v4.value;
|
|
804
843
|
}
|
|
805
|
-
|
|
806
|
-
Object.defineProperties(l3.prototype, { constructor: { configurable: true, value: void 0 }, type: { configurable: true, value:
|
|
844
|
+
y4.displayName = "ReactiveTextNode";
|
|
845
|
+
Object.defineProperties(l3.prototype, { constructor: { configurable: true, value: void 0 }, type: { configurable: true, value: y4 }, props: { configurable: true, get: function() {
|
|
807
846
|
var i4 = this;
|
|
808
847
|
return { data: { get value() {
|
|
809
848
|
return i4.value;
|
|
@@ -887,7 +926,7 @@ g3("diffed", function(i4, n3) {
|
|
|
887
926
|
i4(n3);
|
|
888
927
|
});
|
|
889
928
|
function w4(i4, n3, r4, t4) {
|
|
890
|
-
var o4 = n3 in i4 && void 0 === i4.ownerSVGElement, f4 =
|
|
929
|
+
var o4 = n3 in i4 && void 0 === i4.ownerSVGElement, f4 = y3(r4);
|
|
891
930
|
return { o: function(i5, n4) {
|
|
892
931
|
f4.value = i5;
|
|
893
932
|
t4 = n4;
|
|
@@ -955,7 +994,7 @@ C.prototype.shouldComponentUpdate = function(i4, n3) {
|
|
|
955
994
|
};
|
|
956
995
|
function useSignal(i4, n3) {
|
|
957
996
|
return T2(function() {
|
|
958
|
-
return
|
|
997
|
+
return y3(i4, n3);
|
|
959
998
|
}, []);
|
|
960
999
|
}
|
|
961
1000
|
var q2 = function(i4) {
|
|
@@ -1013,9 +1052,6 @@ function subscribe(onState, onError) {
|
|
|
1013
1052
|
function postTimeZone(timeZone) {
|
|
1014
1053
|
return post("/api/timezone", { timeZone });
|
|
1015
1054
|
}
|
|
1016
|
-
function postSession(body) {
|
|
1017
|
-
return post("/api/session", body);
|
|
1018
|
-
}
|
|
1019
1055
|
function postBoardRefresh() {
|
|
1020
1056
|
return post("/api/board/refresh");
|
|
1021
1057
|
}
|
|
@@ -1074,46 +1110,66 @@ function postObjective(objective, blocker) {
|
|
|
1074
1110
|
function postListCalendars() {
|
|
1075
1111
|
return postJson("/api/calendars/list", {});
|
|
1076
1112
|
}
|
|
1113
|
+
function postSetupAnswer(step, answer) {
|
|
1114
|
+
return post("/api/onboarding/answer", { step, answer });
|
|
1115
|
+
}
|
|
1116
|
+
function postSetupRevise(step, text, draft) {
|
|
1117
|
+
return post("/api/onboarding/revise", { step, text, draft });
|
|
1118
|
+
}
|
|
1119
|
+
function postSetupDetect() {
|
|
1120
|
+
return post("/api/onboarding/detect");
|
|
1121
|
+
}
|
|
1122
|
+
function postSetupRetry(step) {
|
|
1123
|
+
return post("/api/onboarding/retry", { step });
|
|
1124
|
+
}
|
|
1125
|
+
function postSetupFinish() {
|
|
1126
|
+
return post("/api/onboarding/finish");
|
|
1127
|
+
}
|
|
1128
|
+
async function fetchSetupSources() {
|
|
1129
|
+
return jsonFrom(await fetch("/api/onboarding/sources"), "GET /api/onboarding/sources");
|
|
1130
|
+
}
|
|
1077
1131
|
|
|
1078
1132
|
// client/state.ts
|
|
1079
1133
|
var VIEWS = ["today", "board", "tickets", "settings"];
|
|
1080
1134
|
var RUN_PREFIX = "run:";
|
|
1081
1135
|
var PANEL_PREFIX = "panel:";
|
|
1082
1136
|
var VIEW_KEY = "daily-focus:view";
|
|
1083
|
-
var UNATTENDED_SEEN_KEY = "daily-focus:unattended-seen";
|
|
1084
1137
|
var AGENT_RUN_SEEN_KEY = "daily-focus:agent-run-seen";
|
|
1085
|
-
var FOCUS_MODE_KEY = "daily-focus:focus-mode";
|
|
1086
1138
|
var DETAIL_KEY = "daily-focus:detail-for";
|
|
1087
|
-
var state =
|
|
1139
|
+
var state = y3(null);
|
|
1088
1140
|
function createUi(initial = {}) {
|
|
1089
1141
|
return {
|
|
1090
|
-
selectedId:
|
|
1091
|
-
clickedId:
|
|
1092
|
-
pending:
|
|
1093
|
-
menuFor:
|
|
1094
|
-
statusFor:
|
|
1095
|
-
detailFor:
|
|
1096
|
-
noteDraft:
|
|
1097
|
-
assistantDraft:
|
|
1098
|
-
focusField:
|
|
1099
|
-
openDrawers:
|
|
1100
|
-
ticketMode:
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1142
|
+
selectedId: y3(initial.selectedId ?? null),
|
|
1143
|
+
clickedId: y3(initial.clickedId ?? null),
|
|
1144
|
+
pending: y3(initial.pending ?? /* @__PURE__ */ new Set()),
|
|
1145
|
+
menuFor: y3(initial.menuFor ?? null),
|
|
1146
|
+
statusFor: y3(initial.statusFor ?? null),
|
|
1147
|
+
detailFor: y3(initial.detailFor ?? null),
|
|
1148
|
+
noteDraft: y3(initial.noteDraft ?? ""),
|
|
1149
|
+
assistantDraft: y3(initial.assistantDraft ?? ""),
|
|
1150
|
+
focusField: y3(initial.focusField ?? null),
|
|
1151
|
+
openDrawers: y3(initial.openDrawers ?? /* @__PURE__ */ new Set()),
|
|
1152
|
+
ticketMode: y3(initial.ticketMode ?? "sync"),
|
|
1153
|
+
agentReportSeen: y3(initial.agentReportSeen ?? null),
|
|
1154
|
+
messageOpen: y3(initial.messageOpen ?? /* @__PURE__ */ new Map()),
|
|
1155
|
+
view: y3(initial.view ?? "today"),
|
|
1156
|
+
theme: y3(initial.theme ?? "system"),
|
|
1157
|
+
toast: y3(initial.toast ?? null),
|
|
1158
|
+
connectionError: y3(initial.connectionError ?? null),
|
|
1159
|
+
settingsReturn: y3(initial.settingsReturn ?? "today"),
|
|
1160
|
+
settings: y3(initial.settings ?? null),
|
|
1161
|
+
settingsDraft: y3(initial.settingsDraft ?? /* @__PURE__ */ new Map()),
|
|
1162
|
+
settingsError: y3(initial.settingsError ?? null),
|
|
1163
|
+
settingsSaving: y3(initial.settingsSaving ?? false),
|
|
1164
|
+
texts: y3(initial.texts ?? {}),
|
|
1165
|
+
calendars: y3(initial.calendars ?? null),
|
|
1166
|
+
setupDrafts: y3(initial.setupDrafts ?? /* @__PURE__ */ new Map()),
|
|
1167
|
+
setupEditing: y3(initial.setupEditing ?? null),
|
|
1168
|
+
setupConfirm: y3(initial.setupConfirm ?? null),
|
|
1169
|
+
setupBusy: y3(initial.setupBusy ?? null),
|
|
1170
|
+
setupError: y3(initial.setupError ?? null),
|
|
1171
|
+
setupTell: y3(initial.setupTell ?? /* @__PURE__ */ new Map()),
|
|
1172
|
+
setupSources: y3(initial.setupSources ?? null)
|
|
1117
1173
|
};
|
|
1118
1174
|
}
|
|
1119
1175
|
function isView(value) {
|
|
@@ -1125,9 +1181,7 @@ function remembered() {
|
|
|
1125
1181
|
return {
|
|
1126
1182
|
// Settings is a page to visit, so a reload returns to the view underneath.
|
|
1127
1183
|
view: isView(view) && view !== "settings" ? view : "today",
|
|
1128
|
-
unattendedSeen: localStorage.getItem(UNATTENDED_SEEN_KEY),
|
|
1129
1184
|
agentReportSeen: localStorage.getItem(AGENT_RUN_SEEN_KEY),
|
|
1130
|
-
focusMode: localStorage.getItem(FOCUS_MODE_KEY) === "1",
|
|
1131
1185
|
detailFor: sessionStorage.getItem(DETAIL_KEY)
|
|
1132
1186
|
};
|
|
1133
1187
|
} catch {
|
|
@@ -1295,208 +1349,6 @@ function ThemeToggle({ ui: ui2, handlers: handlers2 }) {
|
|
|
1295
1349
|
);
|
|
1296
1350
|
}
|
|
1297
1351
|
|
|
1298
|
-
// client/format.ts
|
|
1299
|
-
var DAY_FORMAT = {
|
|
1300
|
-
weekday: "long",
|
|
1301
|
-
day: "numeric",
|
|
1302
|
-
month: "long"
|
|
1303
|
-
};
|
|
1304
|
-
function formatters(timeZone) {
|
|
1305
|
-
return {
|
|
1306
|
-
time: new Intl.DateTimeFormat(void 0, { timeZone, hour: "2-digit", minute: "2-digit" }),
|
|
1307
|
-
day: new Intl.DateTimeFormat(void 0, { timeZone, ...DAY_FORMAT }),
|
|
1308
|
-
shortDay: new Intl.DateTimeFormat(void 0, { timeZone, day: "numeric", month: "short" }),
|
|
1309
|
-
shortDayYear: new Intl.DateTimeFormat(void 0, { timeZone, day: "numeric", month: "short", year: "numeric" }),
|
|
1310
|
-
weekday: new Intl.DateTimeFormat(void 0, { timeZone, weekday: "long" })
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
var clockFormats = formatters();
|
|
1314
|
-
var dateFormats = formatters("UTC");
|
|
1315
|
-
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
1316
|
-
function formatted(value, kind) {
|
|
1317
|
-
const bare = typeof value === "string" && DATE_ONLY.test(value);
|
|
1318
|
-
const d4 = bare ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : parseDate(value);
|
|
1319
|
-
return d4 && !Number.isNaN(d4.getTime()) ? (bare ? dateFormats : clockFormats)[kind].format(d4) : "";
|
|
1320
|
-
}
|
|
1321
|
-
function parseDate(value) {
|
|
1322
|
-
if (typeof value !== "string" || value === "") return null;
|
|
1323
|
-
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
1324
|
-
if (dateOnly) return new Date(+dateOnly[1], +dateOnly[2] - 1, +dateOnly[3]);
|
|
1325
|
-
const d4 = new Date(value);
|
|
1326
|
-
return Number.isNaN(d4.getTime()) ? null : d4;
|
|
1327
|
-
}
|
|
1328
|
-
function formatTime(value) {
|
|
1329
|
-
return formatted(value, "time");
|
|
1330
|
-
}
|
|
1331
|
-
function formatDay(value) {
|
|
1332
|
-
return formatted(value, "day");
|
|
1333
|
-
}
|
|
1334
|
-
function formatShortDay(value) {
|
|
1335
|
-
return formatted(value, "shortDay");
|
|
1336
|
-
}
|
|
1337
|
-
function formatWeekday(value) {
|
|
1338
|
-
return formatted(value, "weekday");
|
|
1339
|
-
}
|
|
1340
|
-
var dayKeyFmt = null;
|
|
1341
|
-
var calendarZone;
|
|
1342
|
-
function setCalendarZone(timeZone) {
|
|
1343
|
-
if (timeZone === calendarZone) return;
|
|
1344
|
-
calendarZone = timeZone;
|
|
1345
|
-
try {
|
|
1346
|
-
clockFormats = formatters(timeZone);
|
|
1347
|
-
dayKeyFmt = timeZone ? new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }) : null;
|
|
1348
|
-
} catch {
|
|
1349
|
-
dayKeyFmt = null;
|
|
1350
|
-
clockFormats = formatters();
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
function dayKey(d4) {
|
|
1354
|
-
if (dayKeyFmt) {
|
|
1355
|
-
const parts = dayKeyFmt.formatToParts(d4);
|
|
1356
|
-
const part = (type) => parts.find((p5) => p5.type === type)?.value ?? "";
|
|
1357
|
-
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
1358
|
-
}
|
|
1359
|
-
const m4 = String(d4.getMonth() + 1).padStart(2, "0");
|
|
1360
|
-
const day = String(d4.getDate()).padStart(2, "0");
|
|
1361
|
-
return `${d4.getFullYear()}-${m4}-${day}`;
|
|
1362
|
-
}
|
|
1363
|
-
function splitKey(key) {
|
|
1364
|
-
const [y4, m4, d4] = key.split("-").map(Number);
|
|
1365
|
-
return [y4, m4 - 1, d4];
|
|
1366
|
-
}
|
|
1367
|
-
function keyAt(ms) {
|
|
1368
|
-
return new Date(ms).toISOString().slice(0, 10);
|
|
1369
|
-
}
|
|
1370
|
-
function addDays(key, days) {
|
|
1371
|
-
const [y4, m4, d4] = splitKey(key);
|
|
1372
|
-
return keyAt(Date.UTC(y4, m4, d4 + days));
|
|
1373
|
-
}
|
|
1374
|
-
function addMonths(key, months) {
|
|
1375
|
-
const [y4, m4, d4] = splitKey(key);
|
|
1376
|
-
const lastDay = new Date(Date.UTC(y4, m4 + months + 1, 0)).getUTCDate();
|
|
1377
|
-
return keyAt(Date.UTC(y4, m4 + months, Math.min(d4, lastDay)));
|
|
1378
|
-
}
|
|
1379
|
-
function daysFromToday(value, now = /* @__PURE__ */ new Date()) {
|
|
1380
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
1381
|
-
const [y4, m4, d4] = splitKey(value);
|
|
1382
|
-
const [ty, tm, td] = splitKey(dayKey(now));
|
|
1383
|
-
return Math.round((Date.UTC(y4, m4, d4) - Date.UTC(ty, tm, td)) / 864e5);
|
|
1384
|
-
}
|
|
1385
|
-
const target = parseDate(value);
|
|
1386
|
-
if (!target) return null;
|
|
1387
|
-
const a4 = Date.UTC(...splitKey(dayKey(now)));
|
|
1388
|
-
const b4 = Date.UTC(...splitKey(dayKey(target)));
|
|
1389
|
-
return Math.round((b4 - a4) / 864e5);
|
|
1390
|
-
}
|
|
1391
|
-
function relativeDay(value, now = /* @__PURE__ */ new Date()) {
|
|
1392
|
-
const diff = daysFromToday(value, now);
|
|
1393
|
-
if (diff === null) return "";
|
|
1394
|
-
if (diff === 0) return "today";
|
|
1395
|
-
if (diff === 1) return "tomorrow";
|
|
1396
|
-
if (diff === -1) return "yesterday";
|
|
1397
|
-
if (diff > 1 && diff <= 7) return `in ${diff} days`;
|
|
1398
|
-
if (diff < -1 && diff >= -7) return `${Math.abs(diff)} days ago`;
|
|
1399
|
-
return formatShortDay(value);
|
|
1400
|
-
}
|
|
1401
|
-
function formatDuration(minutes) {
|
|
1402
|
-
const h5 = Math.floor(minutes / 60);
|
|
1403
|
-
const m4 = minutes % 60;
|
|
1404
|
-
if (h5 === 0) return `${m4} min`;
|
|
1405
|
-
if (m4 === 0) return `${h5} h`;
|
|
1406
|
-
return `${h5} h ${m4} min`;
|
|
1407
|
-
}
|
|
1408
|
-
function relativeTime(value, now = /* @__PURE__ */ new Date()) {
|
|
1409
|
-
const d4 = parseDate(value);
|
|
1410
|
-
if (!d4) return "";
|
|
1411
|
-
const seconds = Math.round((now.getTime() - d4.getTime()) / 1e3);
|
|
1412
|
-
if (seconds < 60) return "just now";
|
|
1413
|
-
const minutes = Math.round(seconds / 60);
|
|
1414
|
-
if (minutes < 60) return `${minutes} min ago`;
|
|
1415
|
-
const hours = Math.round(minutes / 60);
|
|
1416
|
-
if (hours < 48) return `${hours} h ago`;
|
|
1417
|
-
const days = Math.round(hours / 24);
|
|
1418
|
-
if (days <= 30) return `${days} days ago`;
|
|
1419
|
-
return dayKey(d4).slice(0, 4) === dayKey(now).slice(0, 4) ? formatShortDay(value) : formatted(value, "shortDayYear");
|
|
1420
|
-
}
|
|
1421
|
-
function firstLine(text) {
|
|
1422
|
-
const line = String(text).split("\n").find((l5) => l5.trim() !== "") ?? "";
|
|
1423
|
-
return line.replace(/^[#>*\-\s]+/, "").replace(/[*_`]/g, "").trim();
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// client/timer.ts
|
|
1427
|
-
function clock(seconds) {
|
|
1428
|
-
const total = Math.abs(seconds);
|
|
1429
|
-
const mm = Math.floor(total / 60);
|
|
1430
|
-
const ss = Math.floor(total % 60);
|
|
1431
|
-
return `${seconds < 0 ? "+" : ""}${mm}:${String(ss).padStart(2, "0")}`;
|
|
1432
|
-
}
|
|
1433
|
-
function remainingSeconds(active, nowMs) {
|
|
1434
|
-
return Math.round((new Date(active.endsAt).getTime() - nowMs) / 1e3);
|
|
1435
|
-
}
|
|
1436
|
-
function unattendedNotice(closed, handlers2) {
|
|
1437
|
-
const stoppedAt = formatTime(closed.endedAt);
|
|
1438
|
-
const headline = closed.reason === "away" ? `Timer stopped at ${stoppedAt} \u2014 nothing had touched the machine since.` : `Timer stopped at ${stoppedAt} \u2014 it had been left running.`;
|
|
1439
|
-
return el(
|
|
1440
|
-
"div",
|
|
1441
|
-
{ class: "timer__away", role: "status" },
|
|
1442
|
-
el(
|
|
1443
|
-
"span",
|
|
1444
|
-
{ class: "timer__away-body" },
|
|
1445
|
-
el("span", { class: "timer__away-text" }, headline),
|
|
1446
|
-
el(
|
|
1447
|
-
"span",
|
|
1448
|
-
{ class: "timer__away-detail" },
|
|
1449
|
-
`${formatDuration(closed.actualMinutes)} logged on \u201C${closed.title}\u201D. `,
|
|
1450
|
-
closed.reason === "away" ? "The time you were away is not in the record." : "Treat that as an upper bound, not a measurement."
|
|
1451
|
-
)
|
|
1452
|
-
),
|
|
1453
|
-
el("button", { type: "button", class: "button", onClick: () => handlers2.startSession(closed.id) }, "Pick it back up"),
|
|
1454
|
-
el(
|
|
1455
|
-
"button",
|
|
1456
|
-
{
|
|
1457
|
-
type: "button",
|
|
1458
|
-
class: "icon-button",
|
|
1459
|
-
"aria-label": "Dismiss",
|
|
1460
|
-
onClick: () => handlers2.dismissUnattended(closed.endedAt)
|
|
1461
|
-
},
|
|
1462
|
-
"\u2715"
|
|
1463
|
-
)
|
|
1464
|
-
);
|
|
1465
|
-
}
|
|
1466
|
-
function Timer({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
1467
|
-
const { active, completedToday, minutesToday, unattendedClose } = state2.session;
|
|
1468
|
-
const closed = unattendedClose && ui2.unattendedSeen.value !== unattendedClose.endedAt ? unattendedClose : null;
|
|
1469
|
-
if (!active && completedToday === 0 && !closed) {
|
|
1470
|
-
return el("div", { class: "timer", id: "timer", hidden: true });
|
|
1471
|
-
}
|
|
1472
|
-
const tally = completedToday > 0 ? `${completedToday} session${completedToday === 1 ? "" : "s"} today \xB7 ${formatDuration(minutesToday)}` : null;
|
|
1473
|
-
if (!active) {
|
|
1474
|
-
return el(
|
|
1475
|
-
"div",
|
|
1476
|
-
{ class: "timer", id: "timer", hidden: false, "data-state": closed ? "stopped" : "idle" },
|
|
1477
|
-
closed ? unattendedNotice(closed, handlers2) : null,
|
|
1478
|
-
tally ? el("span", { class: "timer__tally" }, tally) : null
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
const remaining = remainingSeconds(active, ui2.clock.value);
|
|
1482
|
-
const overrun = remaining < 0;
|
|
1483
|
-
return el(
|
|
1484
|
-
"div",
|
|
1485
|
-
{ class: "timer", id: "timer", hidden: false, "data-state": overrun ? "overrun" : "running" },
|
|
1486
|
-
el("span", { class: "timer__clock" }, clock(remaining)),
|
|
1487
|
-
el(
|
|
1488
|
-
"span",
|
|
1489
|
-
{ class: "timer__title" },
|
|
1490
|
-
active.title,
|
|
1491
|
-
overrun ? el("span", { class: "timer__note" }, ` \xB7 ${active.minutes} min in, still counting`) : null,
|
|
1492
|
-
// The one thing a kitchen timer can't tell you.
|
|
1493
|
-
active.collidesWithNextEvent && !overrun ? el("span", { class: "timer__note timer__note--warn" }, ` \xB7 runs into ${formatTime(active.nextEventAt)}`) : null
|
|
1494
|
-
),
|
|
1495
|
-
tally ? el("span", { class: "timer__tally" }, tally) : null,
|
|
1496
|
-
el("button", { type: "button", class: "button", onClick: () => handlers2.stopSession() }, "Stop")
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
1352
|
// client/actions.ts
|
|
1501
1353
|
function afterRender(fn) {
|
|
1502
1354
|
if (typeof requestAnimationFrame === "function") requestAnimationFrame(fn);
|
|
@@ -1550,7 +1402,6 @@ function adoptState(next) {
|
|
|
1550
1402
|
state.value = next;
|
|
1551
1403
|
ui.connectionError.value = null;
|
|
1552
1404
|
});
|
|
1553
|
-
startLocalTick();
|
|
1554
1405
|
checkStore(next.storeId);
|
|
1555
1406
|
checkAssetVersion(next.assetVersion);
|
|
1556
1407
|
}
|
|
@@ -1593,7 +1444,8 @@ var PAST_TENSE = {
|
|
|
1593
1444
|
dismiss: "Dismissed",
|
|
1594
1445
|
snooze: "Snoozed",
|
|
1595
1446
|
reopen: "Restored",
|
|
1596
|
-
note: "Note saved"
|
|
1447
|
+
note: "Note saved",
|
|
1448
|
+
nudge: "Nudged, back tomorrow"
|
|
1597
1449
|
};
|
|
1598
1450
|
var lastAction = null;
|
|
1599
1451
|
var parkedFrom = /* @__PURE__ */ new Map();
|
|
@@ -1605,6 +1457,9 @@ function unpark(id) {
|
|
|
1605
1457
|
function undoLast() {
|
|
1606
1458
|
if (lastAction && lastAction.action !== "reopen") void unpark(lastAction.id);
|
|
1607
1459
|
}
|
|
1460
|
+
function isRemark(action) {
|
|
1461
|
+
return action === "note" || action === "nudge";
|
|
1462
|
+
}
|
|
1608
1463
|
function optimistic(current, id, action, extra) {
|
|
1609
1464
|
const status = action === "reopen" ? "open" : action === "dismiss" ? "dismissed" : action === "done" ? "done" : "snoozed";
|
|
1610
1465
|
const statusAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1631,12 +1486,12 @@ async function applyAction(id, action, extra = {}) {
|
|
|
1631
1486
|
if (row && action === "snooze" && item && (item.status === "done" || item.status === "dismissed")) {
|
|
1632
1487
|
parkedFrom.set(id, item.status === "done" ? "done" : "dismiss");
|
|
1633
1488
|
}
|
|
1634
|
-
if (action
|
|
1489
|
+
if (!isRemark(action)) state.value = optimistic(current, id, action, extra);
|
|
1635
1490
|
});
|
|
1636
1491
|
try {
|
|
1637
1492
|
state.value = await postAction({ id, action, ...extra });
|
|
1638
1493
|
removePending(id);
|
|
1639
|
-
if (action
|
|
1494
|
+
if (isRemark(action)) {
|
|
1640
1495
|
showToast(PAST_TENSE[action]);
|
|
1641
1496
|
return;
|
|
1642
1497
|
}
|
|
@@ -1710,6 +1565,27 @@ async function stopAssistant(id) {
|
|
|
1710
1565
|
showToast(`Could not stop: ${err.message}`);
|
|
1711
1566
|
}
|
|
1712
1567
|
}
|
|
1568
|
+
async function copy(text) {
|
|
1569
|
+
try {
|
|
1570
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
1571
|
+
await navigator.clipboard.writeText(text);
|
|
1572
|
+
} else {
|
|
1573
|
+
const field = document.createElement("textarea");
|
|
1574
|
+
field.value = text;
|
|
1575
|
+
field.setAttribute("readonly", "");
|
|
1576
|
+
field.style.position = "fixed";
|
|
1577
|
+
field.style.opacity = "0";
|
|
1578
|
+
document.body.append(field);
|
|
1579
|
+
field.select();
|
|
1580
|
+
const copied = document.execCommand("copy");
|
|
1581
|
+
field.remove();
|
|
1582
|
+
if (!copied) throw new Error("the browser would not copy");
|
|
1583
|
+
}
|
|
1584
|
+
showToast("Copied");
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
showToast(`Could not copy: ${err.message}`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1713
1589
|
async function runAgent() {
|
|
1714
1590
|
if (!state.value?.agentRun?.enabled || state.value.agentRun.last?.status === "running") return;
|
|
1715
1591
|
const ok = window.confirm(
|
|
@@ -1738,53 +1614,6 @@ async function askAgent(run, text) {
|
|
|
1738
1614
|
showToast(`Could not ask the morning agent: ${err.message}`);
|
|
1739
1615
|
}
|
|
1740
1616
|
}
|
|
1741
|
-
async function changeSession(body) {
|
|
1742
|
-
if (body.action === "start" && "Notification" in window && Notification.permission === "default") {
|
|
1743
|
-
void Notification.requestPermission();
|
|
1744
|
-
}
|
|
1745
|
-
try {
|
|
1746
|
-
state.value = await postSession(body);
|
|
1747
|
-
startLocalTick();
|
|
1748
|
-
} catch (err) {
|
|
1749
|
-
showToast(`Could not change the timer: ${err.message}`);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
var tickTimer = null;
|
|
1753
|
-
var announcedOverrunFor = null;
|
|
1754
|
-
function startLocalTick() {
|
|
1755
|
-
if (tickTimer) clearInterval(tickTimer);
|
|
1756
|
-
tickTimer = setInterval(tick, 1e3);
|
|
1757
|
-
tick();
|
|
1758
|
-
}
|
|
1759
|
-
function tick() {
|
|
1760
|
-
const active = state.value?.session.active;
|
|
1761
|
-
if (!active) {
|
|
1762
|
-
document.title = "Daily Focus";
|
|
1763
|
-
if (tickTimer) clearInterval(tickTimer);
|
|
1764
|
-
tickTimer = null;
|
|
1765
|
-
return;
|
|
1766
|
-
}
|
|
1767
|
-
const nowMs = Date.now();
|
|
1768
|
-
ui.clock.value = nowMs;
|
|
1769
|
-
const remaining = remainingSeconds(active, nowMs);
|
|
1770
|
-
const mm = Math.floor(Math.abs(remaining) / 60);
|
|
1771
|
-
const ss = String(Math.floor(Math.abs(remaining) % 60)).padStart(2, "0");
|
|
1772
|
-
document.title = `${remaining < 0 ? "+" : ""}${mm}:${ss} \xB7 ${active.title}`;
|
|
1773
|
-
if (remaining <= 0 && announcedOverrunFor !== active.startedAt) {
|
|
1774
|
-
announcedOverrunFor = active.startedAt;
|
|
1775
|
-
announceSessionEnd(active);
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
function announceSessionEnd(active) {
|
|
1779
|
-
const label = active.minutes === 1 ? "1 minute" : `${active.minutes} minutes`;
|
|
1780
|
-
showToast(`${label} in \u2014 still on "${active.title}"?`, null);
|
|
1781
|
-
if ("Notification" in window && Notification.permission === "granted") {
|
|
1782
|
-
new Notification("Still on this?", {
|
|
1783
|
-
body: `${label} in on ${active.title}. Keep going, or stop and log it.`,
|
|
1784
|
-
tag: "daily-focus-session"
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
1617
|
function showView(view) {
|
|
1789
1618
|
if (ui.view.value === view) return;
|
|
1790
1619
|
if (state.value && !availableViews(state.value).includes(view)) return;
|
|
@@ -1833,10 +1662,6 @@ function setTicketMode(mode) {
|
|
|
1833
1662
|
closeMenus();
|
|
1834
1663
|
});
|
|
1835
1664
|
}
|
|
1836
|
-
function setFocusMode(on) {
|
|
1837
|
-
ui.focusMode.value = on;
|
|
1838
|
-
remember(FOCUS_MODE_KEY, on ? "1" : null);
|
|
1839
|
-
}
|
|
1840
1665
|
function restartToast(page) {
|
|
1841
1666
|
if (page.restart === "manual") showToast("Saved. Restart the dashboard to apply it.");
|
|
1842
1667
|
else if (page.restart === "waiting" && state.value?.agentRun?.last?.status === "running") {
|
|
@@ -1866,7 +1691,7 @@ async function loadSettings() {
|
|
|
1866
1691
|
showToast(`Could not load the settings: ${err.message}`);
|
|
1867
1692
|
}
|
|
1868
1693
|
}
|
|
1869
|
-
async function sendSettings(values
|
|
1694
|
+
async function sendSettings(values) {
|
|
1870
1695
|
if (ui.settingsSaving.value) return;
|
|
1871
1696
|
n2(() => {
|
|
1872
1697
|
ui.settingsSaving.value = true;
|
|
@@ -1876,17 +1701,14 @@ async function sendSettings(values, fromDraft) {
|
|
|
1876
1701
|
const page = await postSettings(values);
|
|
1877
1702
|
n2(() => {
|
|
1878
1703
|
ui.settings.value = page;
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
ui.settingsDraft.value = draft;
|
|
1883
|
-
}
|
|
1704
|
+
const draft = new Map(ui.settingsDraft.value);
|
|
1705
|
+
for (const [key, value] of Object.entries(values)) if (draft.get(key) === value) draft.delete(key);
|
|
1706
|
+
ui.settingsDraft.value = draft;
|
|
1884
1707
|
});
|
|
1885
1708
|
restartToast(page);
|
|
1886
1709
|
if (page.restart === "waiting") void followRestart();
|
|
1887
1710
|
} catch (err) {
|
|
1888
|
-
|
|
1889
|
-
else showToast(`Could not save: ${err.message}`);
|
|
1711
|
+
ui.settingsError.value = err.message;
|
|
1890
1712
|
} finally {
|
|
1891
1713
|
ui.settingsSaving.value = false;
|
|
1892
1714
|
}
|
|
@@ -1964,6 +1786,60 @@ async function listCalendars() {
|
|
|
1964
1786
|
ui.calendars.value = err.message;
|
|
1965
1787
|
}
|
|
1966
1788
|
}
|
|
1789
|
+
async function setupRequest(step, send) {
|
|
1790
|
+
if (ui.setupBusy.value) return false;
|
|
1791
|
+
n2(() => {
|
|
1792
|
+
ui.setupBusy.value = step;
|
|
1793
|
+
ui.setupError.value = null;
|
|
1794
|
+
});
|
|
1795
|
+
try {
|
|
1796
|
+
adoptState(await send());
|
|
1797
|
+
return true;
|
|
1798
|
+
} catch (err) {
|
|
1799
|
+
ui.setupError.value = { step, message: err.message };
|
|
1800
|
+
return false;
|
|
1801
|
+
} finally {
|
|
1802
|
+
ui.setupBusy.value = null;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
function forgetDraft(step) {
|
|
1806
|
+
if (!ui.setupDrafts.value.has(step)) return;
|
|
1807
|
+
const next = new Map(ui.setupDrafts.value);
|
|
1808
|
+
next.delete(step);
|
|
1809
|
+
ui.setupDrafts.value = next;
|
|
1810
|
+
}
|
|
1811
|
+
async function answerSetup(step, answer) {
|
|
1812
|
+
if (!await setupRequest(step, () => postSetupAnswer(step, answer))) return;
|
|
1813
|
+
n2(() => {
|
|
1814
|
+
forgetDraft(step);
|
|
1815
|
+
if (ui.setupEditing.value === step) ui.setupEditing.value = null;
|
|
1816
|
+
if (ui.setupConfirm.value === step) ui.setupConfirm.value = null;
|
|
1817
|
+
ui.setupSources.value = null;
|
|
1818
|
+
});
|
|
1819
|
+
afterRender(() => {
|
|
1820
|
+
const heading = document.querySelector('.onboarding__card[data-current="true"] h2');
|
|
1821
|
+
heading?.focus({ preventScroll: true });
|
|
1822
|
+
heading?.closest(".onboarding__card")?.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
async function reviseSetup(step, text, draft) {
|
|
1826
|
+
if (!await setupRequest(step, () => postSetupRevise(step, text, draft))) return;
|
|
1827
|
+
n2(() => {
|
|
1828
|
+
forgetDraft(step);
|
|
1829
|
+
if (ui.setupConfirm.value === step) ui.setupConfirm.value = null;
|
|
1830
|
+
const tell = new Map(ui.setupTell.value);
|
|
1831
|
+
tell.delete(step);
|
|
1832
|
+
ui.setupTell.value = tell;
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
async function loadSetupSources() {
|
|
1836
|
+
ui.setupSources.value = "loading";
|
|
1837
|
+
try {
|
|
1838
|
+
ui.setupSources.value = await fetchSetupSources();
|
|
1839
|
+
} catch (err) {
|
|
1840
|
+
ui.setupSources.value = { error: err.message };
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1967
1843
|
function openSettings(section2 = null) {
|
|
1968
1844
|
if (ui.view.value !== "settings") {
|
|
1969
1845
|
try {
|
|
@@ -2059,21 +1935,16 @@ var handlers = {
|
|
|
2059
1935
|
() => document.querySelector('#tickets .item[data-selected="true"]')?.scrollIntoView({ block: "center", behavior: "smooth" })
|
|
2060
1936
|
);
|
|
2061
1937
|
},
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
ui.unattendedSeen.value = endedAt;
|
|
2066
|
-
remember(UNATTENDED_SEEN_KEY, endedAt);
|
|
2067
|
-
},
|
|
2068
|
-
// A nudge on Slack is invisible to GitHub, so it's recorded as a note: the agent
|
|
2069
|
-
// reads it as free text, and the board's nudge timer starts over from it.
|
|
2070
|
-
nudge: (id) => void applyAction(id, "note", { text: "Nudged reviewers" }),
|
|
1938
|
+
// A nudge on Slack is invisible to GitHub, so it's recorded: the board's nudge
|
|
1939
|
+
// timer starts over from it, and the agent reads that the user asked.
|
|
1940
|
+
nudge: (id) => void applyAction(id, "nudge"),
|
|
2071
1941
|
unpark: (id) => void unpark(id),
|
|
2072
1942
|
refreshBoard: () => void refreshBoard(),
|
|
2073
1943
|
refreshTickets: () => void refreshTickets(),
|
|
2074
1944
|
refreshCalendar: () => void refreshCalendar(),
|
|
2075
1945
|
ask: (id, body) => void ask(id, body),
|
|
2076
1946
|
stopAssistant: (id) => void stopAssistant(id),
|
|
1947
|
+
copy: (text) => void copy(text),
|
|
2077
1948
|
runAgent: () => void runAgent(),
|
|
2078
1949
|
stopAgent: () => void stopAgent(),
|
|
2079
1950
|
askAgent: (run, text) => void askAgent(run, text),
|
|
@@ -2092,7 +1963,6 @@ var handlers = {
|
|
|
2092
1963
|
applyTheme(mode);
|
|
2093
1964
|
ui.theme.value = mode;
|
|
2094
1965
|
},
|
|
2095
|
-
setFocusMode,
|
|
2096
1966
|
openHelp: () => document.getElementById("help")?.showModal(),
|
|
2097
1967
|
hideToast,
|
|
2098
1968
|
openSettings,
|
|
@@ -2103,66 +1973,230 @@ var handlers = {
|
|
|
2103
1973
|
ui.settingsDraft.value = /* @__PURE__ */ new Map();
|
|
2104
1974
|
ui.settingsError.value = null;
|
|
2105
1975
|
}),
|
|
2106
|
-
saveSettings: (
|
|
2107
|
-
chooseSettings: (values) => void sendSettings(values, false),
|
|
1976
|
+
saveSettings: () => void sendSettings(Object.fromEntries(ui.settingsDraft.value)),
|
|
2108
1977
|
loadText: (name) => void loadText(name),
|
|
2109
1978
|
editText: (name, text) => patchText(name, { draft: text }),
|
|
2110
1979
|
saveText: (name, overwrite) => void saveText(name, overwrite),
|
|
2111
1980
|
reloadText: (name) => void loadText(name),
|
|
2112
1981
|
saveObjective: (objective, blocker) => void saveObjective(objective, blocker),
|
|
2113
|
-
listCalendars: () => void listCalendars()
|
|
1982
|
+
listCalendars: () => void listCalendars(),
|
|
1983
|
+
answerSetup: (step, answer) => void answerSetup(step, answer),
|
|
1984
|
+
editSetupDraft: (step, answer, version) => {
|
|
1985
|
+
if (answer === null) return forgetDraft(step);
|
|
1986
|
+
const next = new Map(ui.setupDrafts.value);
|
|
1987
|
+
next.set(step, { version, answer });
|
|
1988
|
+
ui.setupDrafts.value = next;
|
|
1989
|
+
},
|
|
1990
|
+
editSetupStep: (step) => n2(() => {
|
|
1991
|
+
ui.setupEditing.value = step;
|
|
1992
|
+
ui.setupError.value = null;
|
|
1993
|
+
}),
|
|
1994
|
+
confirmSetup: (step) => {
|
|
1995
|
+
ui.setupConfirm.value = step;
|
|
1996
|
+
},
|
|
1997
|
+
editSetupTell: (step, text) => {
|
|
1998
|
+
const next = new Map(ui.setupTell.value);
|
|
1999
|
+
next.set(step, text);
|
|
2000
|
+
ui.setupTell.value = next;
|
|
2001
|
+
},
|
|
2002
|
+
reviseSetup: (step, text, draft) => void reviseSetup(step, text, draft),
|
|
2003
|
+
retrySetup: (step) => void setupRequest(step, () => postSetupRetry(step)),
|
|
2004
|
+
detectSetup: () => void setupRequest("detect", () => postSetupDetect()),
|
|
2005
|
+
finishSetup: () => void setupRequest("finish", () => postSetupFinish()).then((finished) => {
|
|
2006
|
+
if (finished && state.value?.restart === "waiting") void followRestart();
|
|
2007
|
+
}),
|
|
2008
|
+
loadSetupSources: () => void loadSetupSources()
|
|
2114
2009
|
};
|
|
2115
2010
|
|
|
2116
|
-
// client/
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
},
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
el("span", { class: "failure__message" }, failure.message)
|
|
2131
|
-
),
|
|
2132
|
-
el(
|
|
2133
|
-
"div",
|
|
2134
|
-
{ class: "failure__body" },
|
|
2135
|
-
el(
|
|
2136
|
-
"p",
|
|
2137
|
-
{ class: "failure__facts" },
|
|
2138
|
-
el("span", null, failureFacts(failure, lastGood, now)),
|
|
2139
|
-
el(
|
|
2140
|
-
"button",
|
|
2141
|
-
{
|
|
2142
|
-
type: "button",
|
|
2143
|
-
class: "button failure__retry",
|
|
2144
|
-
"aria-busy": String(failure.retrying),
|
|
2145
|
-
// Not disabled, as the refresh button isn't: a click mid-attempt is ignored instead.
|
|
2146
|
-
onClick: () => {
|
|
2147
|
-
if (!failure.retrying) onRetry();
|
|
2148
|
-
}
|
|
2149
|
-
},
|
|
2150
|
-
failure.retrying ? "Retrying\u2026" : "Retry now"
|
|
2151
|
-
)
|
|
2152
|
-
),
|
|
2153
|
-
failure.detail ? el("pre", { class: "failure__detail" }, failure.detail) : null
|
|
2154
|
-
)
|
|
2155
|
-
);
|
|
2156
|
-
}
|
|
2157
|
-
function failureFacts(failure, lastGood, now) {
|
|
2158
|
-
const bits = failure.attempts > 1 ? [`Failing since ${when(failure.since, now)}`, `${failure.attempts} attempts`] : [`Failed at ${when(failure.since, now)}`];
|
|
2159
|
-
bits.push(lastGood ? `last good read ${when(lastGood, now)}` : "nothing read yet");
|
|
2160
|
-
if (failure.retrying) bits.push("trying again now");
|
|
2161
|
-
else if (failure.retryAt) bits.push(`next try ${when(failure.retryAt, now)}`);
|
|
2162
|
-
return bits.join(" \xB7 ");
|
|
2011
|
+
// client/format.ts
|
|
2012
|
+
var DAY_FORMAT = {
|
|
2013
|
+
weekday: "long",
|
|
2014
|
+
day: "numeric",
|
|
2015
|
+
month: "long"
|
|
2016
|
+
};
|
|
2017
|
+
function formatters(timeZone) {
|
|
2018
|
+
return {
|
|
2019
|
+
time: new Intl.DateTimeFormat(void 0, { timeZone, hour: "2-digit", minute: "2-digit" }),
|
|
2020
|
+
day: new Intl.DateTimeFormat(void 0, { timeZone, ...DAY_FORMAT }),
|
|
2021
|
+
shortDay: new Intl.DateTimeFormat(void 0, { timeZone, day: "numeric", month: "short" }),
|
|
2022
|
+
shortDayYear: new Intl.DateTimeFormat(void 0, { timeZone, day: "numeric", month: "short", year: "numeric" }),
|
|
2023
|
+
weekday: new Intl.DateTimeFormat(void 0, { timeZone, weekday: "long" })
|
|
2024
|
+
};
|
|
2163
2025
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2026
|
+
var clockFormats = formatters();
|
|
2027
|
+
var dateFormats = formatters("UTC");
|
|
2028
|
+
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
2029
|
+
function formatted(value, kind) {
|
|
2030
|
+
const bare = typeof value === "string" && DATE_ONLY.test(value);
|
|
2031
|
+
const d5 = bare ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : parseDate(value);
|
|
2032
|
+
return d5 && !Number.isNaN(d5.getTime()) ? (bare ? dateFormats : clockFormats)[kind].format(d5) : "";
|
|
2033
|
+
}
|
|
2034
|
+
function parseDate(value) {
|
|
2035
|
+
if (typeof value !== "string" || value === "") return null;
|
|
2036
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
2037
|
+
if (dateOnly) return new Date(+dateOnly[1], +dateOnly[2] - 1, +dateOnly[3]);
|
|
2038
|
+
const d5 = new Date(value);
|
|
2039
|
+
return Number.isNaN(d5.getTime()) ? null : d5;
|
|
2040
|
+
}
|
|
2041
|
+
function formatTime(value) {
|
|
2042
|
+
return formatted(value, "time");
|
|
2043
|
+
}
|
|
2044
|
+
function formatDay(value) {
|
|
2045
|
+
return formatted(value, "day");
|
|
2046
|
+
}
|
|
2047
|
+
function formatShortDay(value) {
|
|
2048
|
+
return formatted(value, "shortDay");
|
|
2049
|
+
}
|
|
2050
|
+
function formatWeekday(value) {
|
|
2051
|
+
return formatted(value, "weekday");
|
|
2052
|
+
}
|
|
2053
|
+
var dayKeyFmt = null;
|
|
2054
|
+
var calendarZone;
|
|
2055
|
+
function setCalendarZone(timeZone) {
|
|
2056
|
+
if (timeZone === calendarZone) return;
|
|
2057
|
+
calendarZone = timeZone;
|
|
2058
|
+
try {
|
|
2059
|
+
clockFormats = formatters(timeZone);
|
|
2060
|
+
dayKeyFmt = timeZone ? new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }) : null;
|
|
2061
|
+
} catch {
|
|
2062
|
+
dayKeyFmt = null;
|
|
2063
|
+
clockFormats = formatters();
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
function dayKey(d5) {
|
|
2067
|
+
if (dayKeyFmt) {
|
|
2068
|
+
const parts = dayKeyFmt.formatToParts(d5);
|
|
2069
|
+
const part = (type) => parts.find((p5) => p5.type === type)?.value ?? "";
|
|
2070
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
2071
|
+
}
|
|
2072
|
+
const m4 = String(d5.getMonth() + 1).padStart(2, "0");
|
|
2073
|
+
const day = String(d5.getDate()).padStart(2, "0");
|
|
2074
|
+
return `${d5.getFullYear()}-${m4}-${day}`;
|
|
2075
|
+
}
|
|
2076
|
+
function splitKey(key) {
|
|
2077
|
+
const [y5, m4, d5] = key.split("-").map(Number);
|
|
2078
|
+
return [y5, m4 - 1, d5];
|
|
2079
|
+
}
|
|
2080
|
+
function keyAt(ms) {
|
|
2081
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
2082
|
+
}
|
|
2083
|
+
function addDays(key, days) {
|
|
2084
|
+
const [y5, m4, d5] = splitKey(key);
|
|
2085
|
+
return keyAt(Date.UTC(y5, m4, d5 + days));
|
|
2086
|
+
}
|
|
2087
|
+
function addMonths(key, months) {
|
|
2088
|
+
const [y5, m4, d5] = splitKey(key);
|
|
2089
|
+
const lastDay = new Date(Date.UTC(y5, m4 + months + 1, 0)).getUTCDate();
|
|
2090
|
+
return keyAt(Date.UTC(y5, m4 + months, Math.min(d5, lastDay)));
|
|
2091
|
+
}
|
|
2092
|
+
function daysFromToday(value, now = /* @__PURE__ */ new Date()) {
|
|
2093
|
+
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
2094
|
+
const [y5, m4, d5] = splitKey(value);
|
|
2095
|
+
const [ty, tm, td] = splitKey(dayKey(now));
|
|
2096
|
+
return Math.round((Date.UTC(y5, m4, d5) - Date.UTC(ty, tm, td)) / 864e5);
|
|
2097
|
+
}
|
|
2098
|
+
const target = parseDate(value);
|
|
2099
|
+
if (!target) return null;
|
|
2100
|
+
const a4 = Date.UTC(...splitKey(dayKey(now)));
|
|
2101
|
+
const b4 = Date.UTC(...splitKey(dayKey(target)));
|
|
2102
|
+
return Math.round((b4 - a4) / 864e5);
|
|
2103
|
+
}
|
|
2104
|
+
function relativeDay(value, now = /* @__PURE__ */ new Date()) {
|
|
2105
|
+
const diff = daysFromToday(value, now);
|
|
2106
|
+
if (diff === null) return "";
|
|
2107
|
+
if (diff === 0) return "today";
|
|
2108
|
+
if (diff === 1) return "tomorrow";
|
|
2109
|
+
if (diff === -1) return "yesterday";
|
|
2110
|
+
if (diff > 1 && diff <= 7) return `in ${diff} days`;
|
|
2111
|
+
if (diff < -1 && diff >= -7) return `${Math.abs(diff)} days ago`;
|
|
2112
|
+
return formatShortDay(value);
|
|
2113
|
+
}
|
|
2114
|
+
function formatDuration(minutes) {
|
|
2115
|
+
const h5 = Math.floor(minutes / 60);
|
|
2116
|
+
const m4 = minutes % 60;
|
|
2117
|
+
if (h5 === 0) return `${m4} min`;
|
|
2118
|
+
if (m4 === 0) return `${h5} h`;
|
|
2119
|
+
return `${h5} h ${m4} min`;
|
|
2120
|
+
}
|
|
2121
|
+
function relativeTime(value, now = /* @__PURE__ */ new Date()) {
|
|
2122
|
+
const d5 = parseDate(value);
|
|
2123
|
+
if (!d5) return "";
|
|
2124
|
+
const seconds = Math.round((now.getTime() - d5.getTime()) / 1e3);
|
|
2125
|
+
if (seconds < 60) return "just now";
|
|
2126
|
+
const minutes = Math.round(seconds / 60);
|
|
2127
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
2128
|
+
const hours = Math.round(minutes / 60);
|
|
2129
|
+
if (hours < 48) return `${hours} h ago`;
|
|
2130
|
+
const days = Math.round(hours / 24);
|
|
2131
|
+
if (days <= 30) return `${days} days ago`;
|
|
2132
|
+
return dayKey(d5).slice(0, 4) === dayKey(now).slice(0, 4) ? formatShortDay(value) : formatted(value, "shortDayYear");
|
|
2133
|
+
}
|
|
2134
|
+
function durationBetween(start, end) {
|
|
2135
|
+
const from = parseDate(start);
|
|
2136
|
+
const to = parseDate(end);
|
|
2137
|
+
if (!from || !to || to < from) return "";
|
|
2138
|
+
const seconds = Math.round((to.getTime() - from.getTime()) / 1e3);
|
|
2139
|
+
return seconds < 60 ? `${seconds} s` : formatDuration(Math.round(seconds / 60));
|
|
2140
|
+
}
|
|
2141
|
+
function formatClock(seconds) {
|
|
2142
|
+
const whole = Math.max(0, Math.floor(seconds));
|
|
2143
|
+
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
2144
|
+
}
|
|
2145
|
+
function firstLine(text) {
|
|
2146
|
+
const line = String(text).split("\n").find((l5) => l5.trim() !== "") ?? "";
|
|
2147
|
+
return line.replace(/^[#>*\-\s]+/, "").replace(/[*_`]/g, "").trim();
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// client/failure.ts
|
|
2151
|
+
function failureNotice(failure, { fold, className, icon, lastGood, now, onRetry }, ui2, handlers2) {
|
|
2152
|
+
return el(
|
|
2153
|
+
"details",
|
|
2154
|
+
{
|
|
2155
|
+
class: `${className} failure`,
|
|
2156
|
+
open: ui2.openDrawers.value.has(fold),
|
|
2157
|
+
onToggle: (event) => handlers2.toggleDrawer(fold, event.currentTarget.open)
|
|
2158
|
+
},
|
|
2159
|
+
el(
|
|
2160
|
+
"summary",
|
|
2161
|
+
{ class: "failure__summary", title: "Show what went wrong" },
|
|
2162
|
+
icon ? el("span", { class: "banner__icon", "aria-hidden": "true" }, icon) : null,
|
|
2163
|
+
// Plain text, never Markdown: it quotes what GitHub, acli and macOS said.
|
|
2164
|
+
el("span", { class: "failure__message" }, failure.message)
|
|
2165
|
+
),
|
|
2166
|
+
el(
|
|
2167
|
+
"div",
|
|
2168
|
+
{ class: "failure__body" },
|
|
2169
|
+
el(
|
|
2170
|
+
"p",
|
|
2171
|
+
{ class: "failure__facts" },
|
|
2172
|
+
el("span", null, failureFacts(failure, lastGood, now)),
|
|
2173
|
+
el(
|
|
2174
|
+
"button",
|
|
2175
|
+
{
|
|
2176
|
+
type: "button",
|
|
2177
|
+
class: "button failure__retry",
|
|
2178
|
+
"aria-busy": String(failure.retrying),
|
|
2179
|
+
// Not disabled, as the refresh button isn't: a click mid-attempt is ignored instead.
|
|
2180
|
+
onClick: () => {
|
|
2181
|
+
if (!failure.retrying) onRetry();
|
|
2182
|
+
}
|
|
2183
|
+
},
|
|
2184
|
+
failure.retrying ? "Retrying\u2026" : "Retry now"
|
|
2185
|
+
)
|
|
2186
|
+
),
|
|
2187
|
+
failure.detail ? el("pre", { class: "failure__detail" }, failure.detail) : null
|
|
2188
|
+
)
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
function failureFacts(failure, lastGood, now) {
|
|
2192
|
+
const bits = failure.attempts > 1 ? [`Failing since ${when(failure.since, now)}`, `${failure.attempts} attempts`] : [`Failed at ${when(failure.since, now)}`];
|
|
2193
|
+
bits.push(lastGood ? `last good read ${when(lastGood, now)}` : "nothing read yet");
|
|
2194
|
+
if (failure.retrying) bits.push("trying again now");
|
|
2195
|
+
else if (failure.retryAt) bits.push(`next try ${when(failure.retryAt, now)}`);
|
|
2196
|
+
return bits.join(" \xB7 ");
|
|
2197
|
+
}
|
|
2198
|
+
function when(value, now) {
|
|
2199
|
+
const days = daysFromToday(value, now);
|
|
2166
2200
|
if (days === 0) return formatTime(value);
|
|
2167
2201
|
return `${days === -1 ? "yesterday" : days === 1 ? "tomorrow" : formatShortDay(value)} ${formatTime(value)}`;
|
|
2168
2202
|
}
|
|
@@ -2306,7 +2340,7 @@ function renderMarkdown(value) {
|
|
|
2306
2340
|
function anchor(href, label) {
|
|
2307
2341
|
return el("a", { href: /^https?:\/\//i.test(href) ? href : "#", target: "_blank", rel: "noopener noreferrer" }, label);
|
|
2308
2342
|
}
|
|
2309
|
-
function renderMarkdownBlocks(value) {
|
|
2343
|
+
function renderMarkdownBlocks(value, options = {}) {
|
|
2310
2344
|
const out = [];
|
|
2311
2345
|
const lines = String(value).replace(/\r\n/g, "\n").split("\n");
|
|
2312
2346
|
let i4 = 0;
|
|
@@ -2317,7 +2351,16 @@ function renderMarkdownBlocks(value) {
|
|
|
2317
2351
|
i4++;
|
|
2318
2352
|
while (i4 < lines.length && !/^\s*```/.test(lines[i4])) code.push(lines[i4++]);
|
|
2319
2353
|
i4++;
|
|
2320
|
-
|
|
2354
|
+
const text = code.join("\n");
|
|
2355
|
+
const { onCopy } = options;
|
|
2356
|
+
out.push(
|
|
2357
|
+
onCopy ? el(
|
|
2358
|
+
"div",
|
|
2359
|
+
{ class: "code-block" },
|
|
2360
|
+
el("pre", null, text),
|
|
2361
|
+
el("button", { type: "button", class: "code-block__copy", title: "Copy to the clipboard", onClick: () => onCopy(text) }, "Copy")
|
|
2362
|
+
) : el("pre", null, text)
|
|
2363
|
+
);
|
|
2321
2364
|
continue;
|
|
2322
2365
|
}
|
|
2323
2366
|
if (line.trim() === "") {
|
|
@@ -2327,7 +2370,7 @@ function renderMarkdownBlocks(value) {
|
|
|
2327
2370
|
if (/^\s*>/.test(line)) {
|
|
2328
2371
|
const quoted = [];
|
|
2329
2372
|
while (i4 < lines.length && /^\s*>/.test(lines[i4])) quoted.push(lines[i4++].replace(/^\s*>\s?/, ""));
|
|
2330
|
-
out.push(el("blockquote", null, renderMarkdownBlocks(quoted.join("\n"))));
|
|
2373
|
+
out.push(el("blockquote", null, renderMarkdownBlocks(quoted.join("\n"), options)));
|
|
2331
2374
|
continue;
|
|
2332
2375
|
}
|
|
2333
2376
|
if (/^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
|
@@ -2571,9 +2614,6 @@ function renderItem(item, state2, ui2, handlers2) {
|
|
|
2571
2614
|
function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
2572
2615
|
const now = new Date(state2.now);
|
|
2573
2616
|
const selected = ui2.selectedId.value === item.id;
|
|
2574
|
-
const active = state2.session?.active ?? null;
|
|
2575
|
-
const running = active !== null && active.id === item.id;
|
|
2576
|
-
const overrun = running && remainingSeconds(active, ui2.clock.value) < 0;
|
|
2577
2617
|
const repo = githubRepo(item);
|
|
2578
2618
|
const title = item.url ? el(
|
|
2579
2619
|
"a",
|
|
@@ -2591,8 +2631,6 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2591
2631
|
"data-selected": String(selected),
|
|
2592
2632
|
"data-priority": String(item.priority !== void 0 && item.status === "open"),
|
|
2593
2633
|
"data-pending": String(ui2.pending.value.has(item.id)),
|
|
2594
|
-
"data-running": String(running),
|
|
2595
|
-
"data-overrun": String(overrun),
|
|
2596
2634
|
"data-detail": String(ui2.detailFor.value === item.id),
|
|
2597
2635
|
// A click on the card opens the panel on it: one way in, and the whole card is it.
|
|
2598
2636
|
onClick: (event) => {
|
|
@@ -2612,31 +2650,21 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2612
2650
|
"div",
|
|
2613
2651
|
{ class: "item__body" },
|
|
2614
2652
|
el("p", { class: "item__title" }, title),
|
|
2615
|
-
renderMeta(item, state2,
|
|
2616
|
-
item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null
|
|
2653
|
+
renderMeta(item, state2, now),
|
|
2654
|
+
item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null,
|
|
2655
|
+
renderAhead(item.id, state2)
|
|
2617
2656
|
),
|
|
2618
|
-
renderActions(item,
|
|
2657
|
+
renderActions(item, ui2, handlers2),
|
|
2619
2658
|
ui2.menuFor.value === item.id ? renderSnoozeMenu(item, now, handlers2) : null
|
|
2620
2659
|
);
|
|
2621
2660
|
}
|
|
2622
|
-
function renderMeta(item, state2,
|
|
2661
|
+
function renderMeta(item, state2, now) {
|
|
2623
2662
|
const pills = [];
|
|
2624
2663
|
const said = /* @__PURE__ */ new Set();
|
|
2625
2664
|
const pill = (cls, text) => {
|
|
2626
2665
|
pills.push(el("span", { class: cls }, text));
|
|
2627
2666
|
said.add(text.trim().toLowerCase());
|
|
2628
2667
|
};
|
|
2629
|
-
const active = state2.session?.active ?? null;
|
|
2630
|
-
if (active && active.id === item.id) {
|
|
2631
|
-
pills.push(
|
|
2632
|
-
el(
|
|
2633
|
-
"span",
|
|
2634
|
-
{ class: "pill pill--running" },
|
|
2635
|
-
el("span", { class: "item__running-clock" }, clock(remainingSeconds(active, ui2.clock.value))),
|
|
2636
|
-
" focusing"
|
|
2637
|
-
)
|
|
2638
|
-
);
|
|
2639
|
-
}
|
|
2640
2668
|
if (item.priority !== void 0 && item.status === "open") {
|
|
2641
2669
|
pill("pill pill--priority", `#${item.priority}`);
|
|
2642
2670
|
}
|
|
@@ -2688,24 +2716,43 @@ function rowPillTexts(row, state2) {
|
|
|
2688
2716
|
function rowPills(row, state2) {
|
|
2689
2717
|
return rowPillTexts(row, state2).map(([cls, text]) => el("span", { class: cls }, text));
|
|
2690
2718
|
}
|
|
2691
|
-
|
|
2719
|
+
var READING = {
|
|
2720
|
+
"assess-review": "Reading the review\u2026",
|
|
2721
|
+
"explain-ci": "Reading the failing checks\u2026"
|
|
2722
|
+
};
|
|
2723
|
+
function renderAhead(id, state2) {
|
|
2724
|
+
const auto = state2.assistant?.auto;
|
|
2725
|
+
const keys = auto?.rows?.[id];
|
|
2726
|
+
if (!auto || !keys?.length) return null;
|
|
2727
|
+
const turn = (state2.assistant.items?.[id]?.turns ?? []).findLast((candidate) => candidate.auto !== null && keys.includes(candidate.auto));
|
|
2728
|
+
const mark = el("span", { class: "item__ahead-mark", "aria-hidden": "true" }, "\u2726");
|
|
2729
|
+
if (!turn) {
|
|
2730
|
+
return auto.enabled && auto.usedToday >= auto.limit ? el("p", { class: "item__ahead item__ahead--quiet" }, mark, `Not answered ahead: today's ${auto.limit} are used.`) : null;
|
|
2731
|
+
}
|
|
2732
|
+
if (turn.status === "running") {
|
|
2733
|
+
return el("p", { class: "item__ahead item__ahead--quiet", "data-status": "running" }, mark, READING[turn.action ?? ""] ?? "Reading\u2026");
|
|
2734
|
+
}
|
|
2735
|
+
if (turn.status === "failed") {
|
|
2736
|
+
return el("p", { class: "item__ahead item__ahead--quiet" }, mark, "Could not answer ahead; the panel says why.");
|
|
2737
|
+
}
|
|
2738
|
+
const verdict = turn.status === "done" ? verdictLine(turn.reply) : "";
|
|
2739
|
+
if (!verdict) return null;
|
|
2740
|
+
return el(
|
|
2741
|
+
"p",
|
|
2742
|
+
{ class: "item__ahead", title: `Answered ahead ${relativeTime(turn.endedAt ?? turn.startedAt, new Date(state2.now))}. Open the panel for the whole answer.` },
|
|
2743
|
+
mark,
|
|
2744
|
+
renderMarkdown(verdict)
|
|
2745
|
+
);
|
|
2746
|
+
}
|
|
2747
|
+
function verdictLine(reply) {
|
|
2748
|
+
const line = reply.split("\n").find((candidate) => candidate.trim() !== "") ?? "";
|
|
2749
|
+
return line.replace(/^\s*(?:#{1,6}\s+|>\s*|[-*•]\s+|\d+[.)]\s+)/, "").trim();
|
|
2750
|
+
}
|
|
2751
|
+
function renderActions(item, ui2, handlers2) {
|
|
2692
2752
|
const buttons = [];
|
|
2693
2753
|
const act = (id, action) => () => handlers2.onAction(id, action);
|
|
2694
|
-
const running = state2.session?.active?.id === item.id;
|
|
2695
2754
|
if (item.status === "open") {
|
|
2696
2755
|
if (item.kind === "task") {
|
|
2697
|
-
buttons.push(
|
|
2698
|
-
el(
|
|
2699
|
-
"button",
|
|
2700
|
-
{
|
|
2701
|
-
type: "button",
|
|
2702
|
-
class: running ? "button button--primary" : "button",
|
|
2703
|
-
title: running ? "Stop the focus session" : "Start a focus session on this",
|
|
2704
|
-
onClick: () => running ? handlers2.stopSession() : handlers2.startSession(item.id)
|
|
2705
|
-
},
|
|
2706
|
-
running ? "Stop" : "Focus"
|
|
2707
|
-
)
|
|
2708
|
-
);
|
|
2709
2756
|
buttons.push(el("button", { type: "button", class: "button", onClick: act(item.id, "done") }, "Done"));
|
|
2710
2757
|
buttons.push(
|
|
2711
2758
|
el(
|
|
@@ -2905,8 +2952,10 @@ function renderBoard(state2, ui2, handlers2) {
|
|
|
2905
2952
|
const open = board.rows.filter((row) => row.status === "open");
|
|
2906
2953
|
for (const court of COURT_ORDER) {
|
|
2907
2954
|
const rows = open.filter((row) => row.court === court);
|
|
2908
|
-
|
|
2909
|
-
|
|
2955
|
+
const nudged = court === "reviewers" ? rows.filter((row) => row.nudgedAt) : [];
|
|
2956
|
+
const waiting = rows.filter((row) => !row.nudgedAt);
|
|
2957
|
+
if (waiting.length > 0) parts.push(section(COURT_TITLE[court], waiting, state2, ui2, handlers2, null, renderPullRow));
|
|
2958
|
+
if (nudged.length > 0) parts.push(section("Nudged", nudged, state2, ui2, handlers2, null, renderPullRow));
|
|
2910
2959
|
}
|
|
2911
2960
|
if (open.length === 0 && !board.reason) {
|
|
2912
2961
|
parts.push(
|
|
@@ -2963,6 +3012,7 @@ function PullRow({ item: row, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2963
3012
|
"data-id": row.id,
|
|
2964
3013
|
"data-status": row.status,
|
|
2965
3014
|
"data-court": row.court,
|
|
3015
|
+
"data-nudged": String(Boolean(row.nudgedAt)),
|
|
2966
3016
|
"data-selected": String(selected),
|
|
2967
3017
|
"data-pending": String(ui2.pending.value.has(row.id)),
|
|
2968
3018
|
"data-detail": String(ui2.detailFor.value === row.id),
|
|
@@ -2991,7 +3041,8 @@ function PullRow({ item: row, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
2991
3041
|
),
|
|
2992
3042
|
renderPullMeta(row, state2, now),
|
|
2993
3043
|
renderPullReasons(row, now),
|
|
2994
|
-
renderCancelledChecks(row)
|
|
3044
|
+
renderCancelledChecks(row),
|
|
3045
|
+
renderAhead(row.id, state2)
|
|
2995
3046
|
),
|
|
2996
3047
|
renderPullActions(row, ui2, handlers2),
|
|
2997
3048
|
ui2.menuFor.value === row.id ? renderSnoozeMenu(row, now, handlers2, { indefinite: false }) : null
|
|
@@ -3010,6 +3061,9 @@ function renderPullMeta(row, state2, now) {
|
|
|
3010
3061
|
if (row.mergeable === "CONFLICTING") pill("pill pill--overdue", "conflicts");
|
|
3011
3062
|
if (row.autoMerge) pill("pill pill--good", "auto-merge on");
|
|
3012
3063
|
if (row.nudge) pill("pill pill--age", "time to ask");
|
|
3064
|
+
if (row.nudgedAt && row.nudgedUntil) {
|
|
3065
|
+
pill("pill", `nudged ${relativeTime(row.nudgedAt, now)}, back ${relativeDay(row.nudgedUntil, now)} ${formatTime(row.nudgedUntil)}`);
|
|
3066
|
+
}
|
|
3013
3067
|
if (row.court === "blocked") {
|
|
3014
3068
|
pill("pill", `blocked ${waitedFor(row.since, now)}`);
|
|
3015
3069
|
} else if (row.court === "gate" || row.court === "checks") {
|
|
@@ -3114,7 +3168,7 @@ function renderPullActions(row, ui2, handlers2) {
|
|
|
3114
3168
|
)
|
|
3115
3169
|
);
|
|
3116
3170
|
if (row.status === "open") {
|
|
3117
|
-
if (row.court === "reviewers") {
|
|
3171
|
+
if (row.court === "reviewers" && !row.nudgedAt) {
|
|
3118
3172
|
buttons.push(
|
|
3119
3173
|
el(
|
|
3120
3174
|
"button",
|
|
@@ -3151,7 +3205,7 @@ var TICKET_COURT_TITLE = {
|
|
|
3151
3205
|
};
|
|
3152
3206
|
var TICKET_COURT_ORDER = ["settled", "started", "idle"];
|
|
3153
3207
|
var TICKET_COURT_HINT = {
|
|
3154
|
-
settled: "Every pull request
|
|
3208
|
+
settled: "Every pull request linked to these is closed. Move them on, or say what is still to come \u2014 a pull request nobody has written yet is invisible from here.",
|
|
3155
3209
|
started: "A pull request is open, so the work has begun. The status still says it has not.",
|
|
3156
3210
|
idle: "Nothing in Jira links any code to these. Fine for work that is not code; worth a look otherwise."
|
|
3157
3211
|
};
|
|
@@ -3362,7 +3416,8 @@ function TicketRowView({ item: row, state: state2, ui: ui2, handlers: handlers2
|
|
|
3362
3416
|
{ class: "item__body" },
|
|
3363
3417
|
renderTicketStatus(row, state2, ui2, handlers2),
|
|
3364
3418
|
el("p", { class: "item__title" }, title),
|
|
3365
|
-
renderTicketMeta(row, state2, handlers2)
|
|
3419
|
+
renderTicketMeta(row, state2, handlers2),
|
|
3420
|
+
renderGitHubEvidence(row, now)
|
|
3366
3421
|
),
|
|
3367
3422
|
renderTicketActions(row, ui2, handlers2),
|
|
3368
3423
|
ui2.menuFor.value === row.id ? renderSnoozeMenu(row, now, handlers2, PARK_MENU) : null
|
|
@@ -3436,6 +3491,25 @@ function renderTicketMeta(row, state2, handlers2) {
|
|
|
3436
3491
|
pills.push(...rowPills(row, state2));
|
|
3437
3492
|
return pills.length > 0 ? el("div", { class: "item__meta item__meta--extra" }, pills) : null;
|
|
3438
3493
|
}
|
|
3494
|
+
var EVIDENCE_SHOWN = 3;
|
|
3495
|
+
function renderGitHubEvidence(row, now) {
|
|
3496
|
+
const pulls = row.pulls ?? [];
|
|
3497
|
+
if (!row.fromGitHub || pulls.length === 0) return null;
|
|
3498
|
+
const parts = ["GitHub, ahead of Jira: "];
|
|
3499
|
+
pulls.slice(0, EVIDENCE_SHOWN).forEach((pull, i4) => {
|
|
3500
|
+
if (i4 > 0) parts.push(" \xB7 ");
|
|
3501
|
+
parts.push(
|
|
3502
|
+
el("a", { href: pull.url, target: "_blank", rel: "noopener noreferrer", title: pull.title }, `${pull.repo}#${pull.number}`),
|
|
3503
|
+
` ${describePull(pull, now)}`
|
|
3504
|
+
);
|
|
3505
|
+
});
|
|
3506
|
+
if (pulls.length > EVIDENCE_SHOWN) parts.push(` \xB7 and ${pulls.length - EVIDENCE_SHOWN} more`);
|
|
3507
|
+
return el("p", { class: "item__reason item__reason--waiting" }, parts);
|
|
3508
|
+
}
|
|
3509
|
+
function describePull(pull, now) {
|
|
3510
|
+
if (pull.state === "open" || pull.state === "draft") return pull.state;
|
|
3511
|
+
return pull.closedAt ? `${pull.state} ${relativeTime(pull.closedAt, now)}` : pull.state;
|
|
3512
|
+
}
|
|
3439
3513
|
function outOfSyncFlag(row, state2, handlers2) {
|
|
3440
3514
|
const hit = state2.tickets?.rows?.find((candidate) => candidate.id === row.id && candidate.status === "open");
|
|
3441
3515
|
if (!hit) return null;
|
|
@@ -3746,8 +3820,6 @@ var SHORTCUTS = [
|
|
|
3746
3820
|
[["Esc"], "Close the menu, then the panel"],
|
|
3747
3821
|
[["o"], "Open the source link"],
|
|
3748
3822
|
[["u"], "Undo the last action"],
|
|
3749
|
-
[["f"], "Focus mode \u2014 hide all but the top item"],
|
|
3750
|
-
[["p"], "Start a focus session on the selected item"],
|
|
3751
3823
|
[["r"], "Refresh whichever board you are on"],
|
|
3752
3824
|
[["w"], "Jira tickets: switch between out of sync and working on"],
|
|
3753
3825
|
[[","], "Settings: your objective, where the agent looks, and the rest"],
|
|
@@ -3777,7 +3849,7 @@ function HelpDialog(_props) {
|
|
|
3777
3849
|
el(
|
|
3778
3850
|
"p",
|
|
3779
3851
|
{ class: "help__note" },
|
|
3780
|
-
`Done, snoozed and dismissed all tell the morning agent to leave an item out of tomorrow's brief. A note is sent along with it as free text. On both boards only snooze and note apply: a pull request stays until it's merged or closed, and a ticket whose status looks wrong is put right in Jira rather than here. Click a card to open its panel, where its notes and the assistant live; the panel stays on the item it was opened for. The assistant is a CLI run on your machine
|
|
3852
|
+
`Done, snoozed and dismissed all tell the morning agent to leave an item out of tomorrow's brief. A note is sent along with it as free text. On both boards only snooze and note apply: a pull request stays until it's merged or closed, and a ticket whose status looks wrong is put right in Jira rather than here. Click a card to open its panel, where its notes and the assistant live; the panel stays on the item it was opened for. The assistant is a CLI run on your machine, told to read and draft, never to send or post. Click "updated \u2026 ago" in the header to open the morning agent's latest run: its report, earlier runs, and a place to ask it why it did what it did.`
|
|
3781
3853
|
),
|
|
3782
3854
|
el("form", { method: "dialog" }, el("button", { type: "submit", class: "button" }, "Close"))
|
|
3783
3855
|
);
|
|
@@ -3941,11 +4013,7 @@ function runStatus(run, now, handlers2) {
|
|
|
3941
4013
|
);
|
|
3942
4014
|
}
|
|
3943
4015
|
function runDuration(run) {
|
|
3944
|
-
|
|
3945
|
-
const to = parseDate(run.endedAt);
|
|
3946
|
-
if (!from || !to || to < from) return "";
|
|
3947
|
-
const seconds = Math.round((to.getTime() - from.getTime()) / 1e3);
|
|
3948
|
-
return seconds < 60 ? `${seconds} s` : formatDuration(Math.round(seconds / 60));
|
|
4016
|
+
return durationBetween(run.startedAt, run.endedAt);
|
|
3949
4017
|
}
|
|
3950
4018
|
function runConversation(run, busy, answering, enabled, ui2, handlers2, inputRef) {
|
|
3951
4019
|
const turns = run.turns.length ? el(
|
|
@@ -4101,8 +4169,9 @@ function RowPanel({ row, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
4101
4169
|
) : el("p", { class: "flyout__empty" }, "Nothing noted yet. A note goes to the morning agent as free text."),
|
|
4102
4170
|
k(NoteForm, { row, title: facts.title, ui: ui2, handlers: handlers2, inputRef: noteInput })
|
|
4103
4171
|
),
|
|
4104
|
-
|
|
4105
|
-
)
|
|
4172
|
+
entry.turns.length ? renderAssistantSection(state2, entry, enabled, handlers2) : null
|
|
4173
|
+
),
|
|
4174
|
+
enabled ? renderAssistantFooter(facts, state2, entry, ui2, handlers2, askInput, row) : null
|
|
4106
4175
|
];
|
|
4107
4176
|
}
|
|
4108
4177
|
function describeRow(row) {
|
|
@@ -4203,21 +4272,27 @@ function NoteForm({
|
|
|
4203
4272
|
el("button", { type: "submit", class: "button button--primary flyout__submit" }, "Save")
|
|
4204
4273
|
);
|
|
4205
4274
|
}
|
|
4206
|
-
function renderAssistantSection(
|
|
4207
|
-
const source = "source" in row ? row.source : "github";
|
|
4208
|
-
const actions = (state2.assistant?.quickActions ?? []).filter(
|
|
4209
|
-
(action) => action.sources === null || action.sources.includes(source)
|
|
4210
|
-
);
|
|
4275
|
+
function renderAssistantSection(state2, entry, enabled, handlers2) {
|
|
4211
4276
|
const label = new Map((state2.assistant?.quickActions ?? []).map((action) => [action.id, action]));
|
|
4212
4277
|
return el(
|
|
4213
4278
|
"section",
|
|
4214
4279
|
{ class: "flyout__section assistant", "data-running": String(entry.running) },
|
|
4215
4280
|
el("h3", { class: "flyout__label" }, "Assistant"),
|
|
4216
|
-
|
|
4281
|
+
el(
|
|
4217
4282
|
"div",
|
|
4218
4283
|
{ class: "assistant__turns" },
|
|
4219
|
-
entry.turns.map((turn) => renderAssistantTurn(turn, label))
|
|
4220
|
-
)
|
|
4284
|
+
entry.turns.map((turn) => renderAssistantTurn(turn, label, handlers2))
|
|
4285
|
+
),
|
|
4286
|
+
enabled ? null : el("p", { class: "flyout__empty" }, "The assistant is switched off (DAILY_FOCUS_ASSISTANT), so nothing more can be asked here.")
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
function renderAssistantFooter(facts, state2, entry, ui2, handlers2, inputRef, row) {
|
|
4290
|
+
const actions = (state2.assistant?.quickActions ?? []).filter(
|
|
4291
|
+
(action) => action.sources === null || action.sources.includes(facts.source)
|
|
4292
|
+
);
|
|
4293
|
+
return el(
|
|
4294
|
+
"div",
|
|
4295
|
+
{ class: "flyout__footer" },
|
|
4221
4296
|
entry.running ? null : el(
|
|
4222
4297
|
"div",
|
|
4223
4298
|
{ class: "assistant__quick" },
|
|
@@ -4237,20 +4312,6 @@ function renderAssistantSection(row, facts, state2, entry, ui2, handlers2, input
|
|
|
4237
4312
|
k(AssistantComposer, { row, title: facts.title, entry, ui: ui2, handlers: handlers2, inputRef })
|
|
4238
4313
|
);
|
|
4239
4314
|
}
|
|
4240
|
-
function renderAssistantHistory(state2, entry) {
|
|
4241
|
-
const label = new Map((state2.assistant?.quickActions ?? []).map((action) => [action.id, action]));
|
|
4242
|
-
return el(
|
|
4243
|
-
"section",
|
|
4244
|
-
{ class: "flyout__section assistant", "data-running": "false" },
|
|
4245
|
-
el("h3", { class: "flyout__label" }, "Assistant"),
|
|
4246
|
-
el(
|
|
4247
|
-
"div",
|
|
4248
|
-
{ class: "assistant__turns" },
|
|
4249
|
-
entry.turns.map((turn) => renderAssistantTurn(turn, label))
|
|
4250
|
-
),
|
|
4251
|
-
el("p", { class: "flyout__empty" }, "The assistant is switched off (DAILY_FOCUS_ASSISTANT), so nothing more can be asked here.")
|
|
4252
|
-
);
|
|
4253
|
-
}
|
|
4254
4315
|
function AssistantComposer({
|
|
4255
4316
|
row,
|
|
4256
4317
|
title,
|
|
@@ -4279,7 +4340,7 @@ function AssistantComposer({
|
|
|
4279
4340
|
rows: 2,
|
|
4280
4341
|
ref: inputRef,
|
|
4281
4342
|
value: ui2.assistantDraft.value,
|
|
4282
|
-
placeholder: entry.turns.length ? "Follow up\u2026" : "
|
|
4343
|
+
placeholder: entry.turns.length ? "Follow up\u2026" : "Ask the assistant about this\u2026",
|
|
4283
4344
|
"aria-label": `Ask the assistant about ${title}`,
|
|
4284
4345
|
disabled: entry.running,
|
|
4285
4346
|
onInput: (event) => {
|
|
@@ -4295,17 +4356,506 @@ function AssistantComposer({
|
|
|
4295
4356
|
entry.running ? el("button", { type: "button", class: "button flyout__submit", onClick: () => handlers2.stopAssistant(row.id) }, "Stop") : el("button", { type: "submit", class: "button button--primary flyout__submit" }, "Send")
|
|
4296
4357
|
);
|
|
4297
4358
|
}
|
|
4298
|
-
function renderAssistantTurn(turn, label) {
|
|
4359
|
+
function renderAssistantTurn(turn, label, handlers2) {
|
|
4299
4360
|
const action = turn.action ? label.get(turn.action) : null;
|
|
4300
4361
|
const extra = action && turn.request.startsWith(action.request) ? turn.request.slice(action.request.length).trim() : null;
|
|
4301
4362
|
const asked = action ? [el("strong", null, action.label), extra ? ` \u2014 ${extra}` : ""] : [turn.request];
|
|
4302
|
-
|
|
4363
|
+
if (turn.auto) asked.push(el("span", { class: "assistant__auto", title: "The dashboard asked this on its own, when the pull request started waiting on you." }, " \xB7 asked ahead"));
|
|
4364
|
+
const { body, created } = turn.status === "done" ? splitCreated(turn.reply) : { body: turn.reply, created: [] };
|
|
4365
|
+
const status = turn.status === "failed" ? el("p", { class: "assistant__status assistant__status--bad" }, `Failed: ${turn.error}`) : turn.status === "aborted" ? el("p", { class: "assistant__status" }, `Stopped: ${turn.error}`) : null;
|
|
4303
4366
|
return el(
|
|
4304
4367
|
"div",
|
|
4305
|
-
{ class: "assistant__turn", "data-status": turn.status },
|
|
4368
|
+
{ key: turn.id, class: "assistant__turn", "data-status": turn.status },
|
|
4306
4369
|
el("p", { class: "assistant__request" }, asked),
|
|
4307
|
-
|
|
4308
|
-
|
|
4370
|
+
renderWork(turn),
|
|
4371
|
+
body ? el("div", { class: "assistant__reply" }, renderMarkdownBlocks(body, { onCopy: (text) => handlers2.copy(text) })) : null,
|
|
4372
|
+
status,
|
|
4373
|
+
turn.status === "done" && (body || created.length) ? el(
|
|
4374
|
+
"div",
|
|
4375
|
+
{ class: "assistant__after" },
|
|
4376
|
+
created.map(
|
|
4377
|
+
(link) => el("a", { class: "button", href: link.url, target: "_blank", rel: "noopener noreferrer", title: link.url }, `${link.label} \u2197`)
|
|
4378
|
+
),
|
|
4379
|
+
body ? el("button", { type: "button", class: "button", onClick: () => handlers2.copy(body) }, "Copy reply") : null
|
|
4380
|
+
) : null
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
function renderWork(turn) {
|
|
4384
|
+
const steps2 = turn.steps ?? [];
|
|
4385
|
+
if (turn.status === "running") {
|
|
4386
|
+
return el(
|
|
4387
|
+
"div",
|
|
4388
|
+
{ class: "assistant__work" },
|
|
4389
|
+
el("p", { class: "assistant__status assistant__status--working" }, "Working \xB7 ", k(Elapsed, { since: turn.startedAt })),
|
|
4390
|
+
steps2.length ? renderSteps(steps2) : null
|
|
4391
|
+
);
|
|
4392
|
+
}
|
|
4393
|
+
const took = durationBetween(turn.startedAt, turn.endedAt);
|
|
4394
|
+
if (!steps2.length) return took ? el("p", { class: "assistant__took" }, `Answered in ${took}`) : null;
|
|
4395
|
+
const failed = steps2.filter((step) => step.state === "failed").length;
|
|
4396
|
+
const summary = [
|
|
4397
|
+
took ? `${turn.status === "done" ? "Worked" : "Ran"} for ${took}` : null,
|
|
4398
|
+
`${steps2.length} ${steps2.length === 1 ? "step" : "steps"}`,
|
|
4399
|
+
failed ? `${failed} failed` : null
|
|
4400
|
+
].filter(Boolean).join(" \xB7 ");
|
|
4401
|
+
return el("details", { class: "assistant__work", open: turn.status === "failed" }, el("summary", null, summary), renderSteps(steps2));
|
|
4402
|
+
}
|
|
4403
|
+
function renderSteps(steps2) {
|
|
4404
|
+
return el(
|
|
4405
|
+
"ol",
|
|
4406
|
+
{ class: "assistant__steps" },
|
|
4407
|
+
steps2.map(
|
|
4408
|
+
(step, index) => step.kind === "message" ? el("li", { key: index, class: "assistant__step assistant__step--message" }, renderMarkdown(step.text.trim())) : el(
|
|
4409
|
+
"li",
|
|
4410
|
+
{ key: index, class: "assistant__step", "data-state": step.state },
|
|
4411
|
+
el("span", { class: "assistant__step-text" }, step.text),
|
|
4412
|
+
step.detail ? el("code", { class: "assistant__step-detail", title: step.detail }, step.detail) : null
|
|
4413
|
+
)
|
|
4414
|
+
)
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
function Elapsed({ since }) {
|
|
4418
|
+
const [now, setNow] = d2(() => Date.now());
|
|
4419
|
+
h2(() => {
|
|
4420
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
4421
|
+
return () => clearInterval(timer);
|
|
4422
|
+
}, []);
|
|
4423
|
+
const start = parseDate(since)?.getTime() ?? now;
|
|
4424
|
+
return el("span", { class: "assistant__clock" }, formatClock((now - start) / 1e3));
|
|
4425
|
+
}
|
|
4426
|
+
var CREATED = /^\s*(?:[-*]\s+)?Created:\s*\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)\s*\.?\s*$/i;
|
|
4427
|
+
function splitCreated(reply) {
|
|
4428
|
+
const created = [];
|
|
4429
|
+
const kept = [];
|
|
4430
|
+
for (const line of reply.split("\n")) {
|
|
4431
|
+
const match = CREATED.exec(line);
|
|
4432
|
+
if (match) created.push({ label: match[1].trim(), url: match[2] });
|
|
4433
|
+
else kept.push(line);
|
|
4434
|
+
}
|
|
4435
|
+
return { body: created.length ? kept.join("\n").trim() : reply, created };
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
// client/onboarding.ts
|
|
4439
|
+
var SECONDS_PER_STEP = 20;
|
|
4440
|
+
function OnboardingView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
4441
|
+
const flow = state2.onboarding;
|
|
4442
|
+
const open = flow.steps.filter((step) => step.status === "open");
|
|
4443
|
+
const editing = ui2.setupEditing.value;
|
|
4444
|
+
h2(() => {
|
|
4445
|
+
if (!flow.detected) handlers2.detectSetup();
|
|
4446
|
+
}, [flow.detected]);
|
|
4447
|
+
return el(
|
|
4448
|
+
"div",
|
|
4449
|
+
{ class: "onboarding" },
|
|
4450
|
+
el(
|
|
4451
|
+
"header",
|
|
4452
|
+
{ class: "onboarding__bar" },
|
|
4453
|
+
el("span", { class: "onboarding__name" }, "Daily Focus"),
|
|
4454
|
+
progress(flow)
|
|
4455
|
+
),
|
|
4456
|
+
el(
|
|
4457
|
+
"main",
|
|
4458
|
+
{ class: "onboarding__column", id: "main" },
|
|
4459
|
+
flow.steps.length > 0 && flow.steps.every((step) => step.status === "open") && flow.steps[0].id === flow.current ? el(
|
|
4460
|
+
"p",
|
|
4461
|
+
{ class: "onboarding__lede" },
|
|
4462
|
+
"Each morning an agent reads your mail, calendar, pull requests and the rest, and writes a short brief of what needs doing today. A few questions get it ready; most are answered already, so confirm or change them."
|
|
4463
|
+
) : null,
|
|
4464
|
+
el(
|
|
4465
|
+
"ol",
|
|
4466
|
+
{ class: "onboarding__thread" },
|
|
4467
|
+
flow.steps.map((step) => {
|
|
4468
|
+
if (step.id === flow.current || step.id === editing) {
|
|
4469
|
+
return el("li", { key: step.id }, k(StepCard, { step, ui: ui2, handlers: handlers2, current: step.id === flow.current }));
|
|
4470
|
+
}
|
|
4471
|
+
if (step.status === "open") return null;
|
|
4472
|
+
return el("li", { key: step.id }, doneRow(step, handlers2, ui2.setupBusy.value !== null));
|
|
4473
|
+
}),
|
|
4474
|
+
flow.current === null ? el("li", { key: "finish" }, k(FinishCard, { flow, ui: ui2, handlers: handlers2 })) : ahead(flow, open)
|
|
4475
|
+
)
|
|
4476
|
+
)
|
|
4477
|
+
);
|
|
4478
|
+
}
|
|
4479
|
+
function progress(flow) {
|
|
4480
|
+
const total = flow.steps.length + 1;
|
|
4481
|
+
const left = flow.steps.filter((step) => step.status === "open").length;
|
|
4482
|
+
const seconds = left * SECONDS_PER_STEP;
|
|
4483
|
+
const text = flow.current === null ? "Ready when you are" : seconds < 60 ? "Under a minute left" : seconds < 120 ? "About a minute left" : `About ${Math.round(seconds / 60)} minutes left`;
|
|
4484
|
+
const done = total - left - (flow.current === null ? 0 : 1);
|
|
4485
|
+
return el(
|
|
4486
|
+
"div",
|
|
4487
|
+
{ class: "onboarding__progress" },
|
|
4488
|
+
el(
|
|
4489
|
+
"div",
|
|
4490
|
+
{ class: "onboarding__track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": total, "aria-valuenow": done, "aria-label": "Setup" },
|
|
4491
|
+
el("i", { style: { width: `${Math.round(done / total * 100)}%` } })
|
|
4492
|
+
),
|
|
4493
|
+
el("span", null, text)
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
function doneRow(step, handlers2, busy) {
|
|
4497
|
+
return el(
|
|
4498
|
+
"div",
|
|
4499
|
+
{ class: "onboarding__done", "data-status": step.status, "data-step": step.id },
|
|
4500
|
+
el("span", { class: "onboarding__dot", "aria-hidden": "true" }),
|
|
4501
|
+
el("span", { class: "onboarding__summary" }, step.summary ?? `${step.topic[0].toUpperCase()}${step.topic.slice(1)}: decide later.`),
|
|
4502
|
+
step.locked ? el("span", { class: "onboarding__locked", title: "Set in the environment the dashboard was started with" }, "Set by the environment") : el("button", { type: "button", class: "onboarding__change", disabled: busy, onClick: () => handlers2.editSetupStep(step.id) }, "Change")
|
|
4503
|
+
);
|
|
4504
|
+
}
|
|
4505
|
+
function ahead(flow, open) {
|
|
4506
|
+
const rest = open.filter((step) => step.id !== flow.current);
|
|
4507
|
+
const looking = rest.find((step) => step.proposal?.status === "working");
|
|
4508
|
+
const topics = rest.filter((step) => step !== looking).map((step) => step.topic);
|
|
4509
|
+
const later = topics.length > 4 ? [...topics.slice(0, 3), `${topics.length - 3} more`] : topics;
|
|
4510
|
+
return el(
|
|
4511
|
+
"li",
|
|
4512
|
+
{ key: "ahead", class: "onboarding__ahead" },
|
|
4513
|
+
looking ? el(
|
|
4514
|
+
"div",
|
|
4515
|
+
{ class: "onboarding__next" },
|
|
4516
|
+
el("span", { class: "onboarding__dot onboarding__dot--looking", "aria-hidden": "true" }),
|
|
4517
|
+
el("p", null, `Next, ${looking.topic}. It's already looking:`),
|
|
4518
|
+
steps(looking)
|
|
4519
|
+
) : null,
|
|
4520
|
+
el(
|
|
4521
|
+
"p",
|
|
4522
|
+
{ class: "onboarding__then" },
|
|
4523
|
+
el("span", { class: "onboarding__dot onboarding__dot--later", "aria-hidden": "true" }),
|
|
4524
|
+
later.length ? `Then ${listed([...later, "your first brief"])}.` : "Then your first brief."
|
|
4525
|
+
)
|
|
4526
|
+
);
|
|
4527
|
+
}
|
|
4528
|
+
function steps(step, count = 3) {
|
|
4529
|
+
const shown = (step.proposal?.steps ?? []).filter((s4) => s4.kind === "tool").slice(-count);
|
|
4530
|
+
const waiting = step.proposal?.status === "working" && !shown.some((s4) => s4.state === "running");
|
|
4531
|
+
return el(
|
|
4532
|
+
"ul",
|
|
4533
|
+
{ class: "onboarding__steps" },
|
|
4534
|
+
shown.map((s4, index) => el("li", { key: index, "data-state": s4.state, title: s4.detail ?? void 0 }, s4.text)),
|
|
4535
|
+
waiting ? el("li", { key: "waiting", "data-state": "running" }, shown.length ? "Writing its answer\u2026" : "Starting\u2026") : null
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
function listed(names) {
|
|
4539
|
+
if (names.length <= 1) return names.join("");
|
|
4540
|
+
return `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
4541
|
+
}
|
|
4542
|
+
function baseAnswer(step) {
|
|
4543
|
+
if (step.answer && step.answer.later !== true) return step.answer;
|
|
4544
|
+
return {
|
|
4545
|
+
choices: Object.fromEntries(step.card.choices.map((choice) => [choice.id, choice.selected])),
|
|
4546
|
+
fields: Object.fromEntries(step.card.fields.map((field) => [field.id, field.value]))
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
function StepCard({ step, ui: ui2, handlers: handlers2, current }) {
|
|
4550
|
+
const saved = ui2.setupDrafts.value.get(step.id);
|
|
4551
|
+
const draft = saved?.answer ?? baseAnswer(step);
|
|
4552
|
+
const stale = saved !== void 0 && saved.version !== step.card.version;
|
|
4553
|
+
const busy = ui2.setupBusy.value !== null;
|
|
4554
|
+
const error = ui2.setupError.value?.step === step.id ? ui2.setupError.value.message : null;
|
|
4555
|
+
const editing = ui2.setupEditing.value === step.id;
|
|
4556
|
+
const proposal = step.proposal;
|
|
4557
|
+
const looking = proposal?.status === "working";
|
|
4558
|
+
const edit = (next) => handlers2.editSetupDraft(step.id, next, saved?.version ?? step.card.version);
|
|
4559
|
+
const tell = ui2.setupTell.value.get(step.id) ?? "";
|
|
4560
|
+
const empty = step.card.choices.every((choice) => choice.options.length === 0) && step.card.fields.length === 0;
|
|
4561
|
+
const conversation = proposal?.conversation ?? [];
|
|
4562
|
+
const revising = conversation.at(-1)?.from === "you";
|
|
4563
|
+
const confirming = looking && ui2.setupConfirm.value === step.id;
|
|
4564
|
+
const held = busy ? "Saving your last answer\u2026" : empty && looking ? "Still looking; this fills in when it is done" : empty && step.card.problem?.blocking ? "Fix what it says above, or decide later" : null;
|
|
4565
|
+
return el(
|
|
4566
|
+
"section",
|
|
4567
|
+
{ class: "onboarding__card", "data-current": String(current), "data-step": step.id, "aria-labelledby": `setup-${step.id}` },
|
|
4568
|
+
el("span", { class: "onboarding__dot onboarding__dot--open", "aria-hidden": "true" }),
|
|
4569
|
+
el("h2", { id: `setup-${step.id}`, tabIndex: -1 }, step.title),
|
|
4570
|
+
conversation.length ? el(
|
|
4571
|
+
"ol",
|
|
4572
|
+
{ class: "onboarding__talk" },
|
|
4573
|
+
conversation.map(
|
|
4574
|
+
(line, index) => line.from === "it" ? el("li", { key: index, class: "onboarding__why" }, sparkle(), el("span", null, line.text)) : el("li", { key: index, class: "onboarding__said" }, line.text)
|
|
4575
|
+
)
|
|
4576
|
+
) : step.card.why && !(looking && empty) ? el("p", { class: "onboarding__why" }, sparkle(), el("span", null, step.card.why)) : null,
|
|
4577
|
+
looking ? el(
|
|
4578
|
+
"div",
|
|
4579
|
+
{ class: "onboarding__looking", role: "status" },
|
|
4580
|
+
revising ? null : el("p", null, empty ? `Looking into ${step.topic}\u2026` : "Still looking. You can answer now, or wait for its pick."),
|
|
4581
|
+
steps(step)
|
|
4582
|
+
) : null,
|
|
4583
|
+
// Under what it said, so a reply reads as one, and what comes back lands here too.
|
|
4584
|
+
step.card.revisable && !looking ? el(
|
|
4585
|
+
"form",
|
|
4586
|
+
{
|
|
4587
|
+
class: "onboarding__tell",
|
|
4588
|
+
onSubmit: (event) => {
|
|
4589
|
+
event.preventDefault();
|
|
4590
|
+
if (tell.trim() && !busy) handlers2.reviseSetup(step.id, tell, draft);
|
|
4591
|
+
}
|
|
4592
|
+
},
|
|
4593
|
+
el("input", {
|
|
4594
|
+
type: "text",
|
|
4595
|
+
value: tell,
|
|
4596
|
+
"aria-label": step.card.reply ?? "Tell it what to change",
|
|
4597
|
+
placeholder: step.card.reply ?? "Tell it what to change",
|
|
4598
|
+
onInput: (event) => handlers2.editSetupTell(step.id, event.currentTarget.value)
|
|
4599
|
+
}),
|
|
4600
|
+
el("button", { type: "submit", class: "button", disabled: busy || tell.trim() === "" }, "Send")
|
|
4601
|
+
) : null,
|
|
4602
|
+
proposal?.status === "failed" ? el(
|
|
4603
|
+
"p",
|
|
4604
|
+
{ class: "onboarding__note" },
|
|
4605
|
+
`It couldn't look into this: ${proposal.error}. `,
|
|
4606
|
+
el("button", { type: "button", class: "link-button", disabled: busy, onClick: () => handlers2.retrySetup(step.id) }, "Try again")
|
|
4607
|
+
) : null,
|
|
4608
|
+
stale ? el(
|
|
4609
|
+
"p",
|
|
4610
|
+
{ class: "onboarding__note" },
|
|
4611
|
+
"It has a new suggestion since you started changing this. ",
|
|
4612
|
+
el("button", { type: "button", class: "link-button", onClick: () => handlers2.editSetupDraft(step.id, null, 0) }, "Use it")
|
|
4613
|
+
) : null,
|
|
4614
|
+
step.card.problem?.blocking ? problem(step, busy, handlers2) : null,
|
|
4615
|
+
el(
|
|
4616
|
+
"form",
|
|
4617
|
+
{
|
|
4618
|
+
class: "onboarding__form",
|
|
4619
|
+
onSubmit: (event) => {
|
|
4620
|
+
event.preventDefault();
|
|
4621
|
+
if (busy) return;
|
|
4622
|
+
if (looking && !confirming) return handlers2.confirmSetup(step.id);
|
|
4623
|
+
handlers2.answerSetup(step.id, draft);
|
|
4624
|
+
}
|
|
4625
|
+
},
|
|
4626
|
+
// Fields first: a card with both asks for the few words before the list.
|
|
4627
|
+
step.card.fields.length ? el(
|
|
4628
|
+
"div",
|
|
4629
|
+
{ class: "onboarding__fields" },
|
|
4630
|
+
step.card.fields.map(
|
|
4631
|
+
(field) => fieldView(field, draft.fields[field.id] ?? "", (value) => edit({ ...draft, fields: { ...draft.fields, [field.id]: value } }))
|
|
4632
|
+
)
|
|
4633
|
+
) : null,
|
|
4634
|
+
step.card.choices.map(
|
|
4635
|
+
(choice) => k(ChoiceView, {
|
|
4636
|
+
choice,
|
|
4637
|
+
picked: draft.choices[choice.id] ?? [],
|
|
4638
|
+
onPick: (values) => edit({ ...draft, choices: { ...draft.choices, [choice.id]: values } })
|
|
4639
|
+
})
|
|
4640
|
+
),
|
|
4641
|
+
step.card.problem && !step.card.problem.blocking ? problem(step, busy, handlers2) : null,
|
|
4642
|
+
error ? el("p", { class: "onboarding__error", role: "alert" }, error) : null,
|
|
4643
|
+
confirming ? el(
|
|
4644
|
+
"div",
|
|
4645
|
+
{ class: "onboarding__actions onboarding__confirm", role: "alert" },
|
|
4646
|
+
el("p", null, revising ? "It\u2019s still working on your change." : "It\u2019s still filling this in.", " Continue with what\u2019s here, and stop it?"),
|
|
4647
|
+
el("button", { type: "submit", class: "button button--primary", disabled: busy }, "Continue without it"),
|
|
4648
|
+
el("button", { type: "button", class: "button onboarding__quiet", onClick: () => handlers2.confirmSetup(null) }, "Wait for it")
|
|
4649
|
+
) : el(
|
|
4650
|
+
"div",
|
|
4651
|
+
{ class: "onboarding__actions" },
|
|
4652
|
+
el(
|
|
4653
|
+
"button",
|
|
4654
|
+
{ type: "submit", class: "button button--primary", disabled: held !== null, title: held ?? void 0 },
|
|
4655
|
+
ui2.setupBusy.value === step.id ? "Saving\u2026" : step.card.empty && step.card.fields.every((field) => !(draft.fields[field.id] ?? "").trim()) ? step.card.empty : "Continue"
|
|
4656
|
+
),
|
|
4657
|
+
step.card.later ? el(
|
|
4658
|
+
"button",
|
|
4659
|
+
{ type: "button", class: "button onboarding__quiet", disabled: busy, onClick: () => handlers2.answerSetup(step.id, { later: true }) },
|
|
4660
|
+
step.card.later
|
|
4661
|
+
) : null,
|
|
4662
|
+
editing ? el("button", { type: "button", class: "button onboarding__quiet", onClick: () => handlers2.editSetupStep(null) }, "Cancel") : null
|
|
4663
|
+
)
|
|
4664
|
+
)
|
|
4665
|
+
);
|
|
4666
|
+
}
|
|
4667
|
+
function problem(step, busy, handlers2) {
|
|
4668
|
+
const found = step.card.problem;
|
|
4669
|
+
return el(
|
|
4670
|
+
"div",
|
|
4671
|
+
{ class: "onboarding__problem", "data-blocking": String(found.blocking) },
|
|
4672
|
+
el("p", null, found.message),
|
|
4673
|
+
found.links?.length ? el(
|
|
4674
|
+
"p",
|
|
4675
|
+
{ class: "onboarding__links" },
|
|
4676
|
+
found.links.map((link) => el("a", { key: link.url, href: link.url, target: "_blank", rel: "noopener" }, link.label))
|
|
4677
|
+
) : null,
|
|
4678
|
+
found.command ? el("pre", null, el("code", null, found.command)) : null,
|
|
4679
|
+
el("button", { type: "button", class: "button", disabled: busy, onClick: () => handlers2.retrySetup(step.id) }, "Check again")
|
|
4680
|
+
);
|
|
4681
|
+
}
|
|
4682
|
+
function sparkle() {
|
|
4683
|
+
return el(
|
|
4684
|
+
"svg",
|
|
4685
|
+
{ class: "onboarding__sparkle", width: 14, height: 14, viewBox: "0 0 14 14", "aria-hidden": "true" },
|
|
4686
|
+
el("path", { d: "M7 0l1.6 5.4L14 7l-5.4 1.6L7 14l-1.6-5.4L0 7l5.4-1.6z", fill: "currentColor" })
|
|
4687
|
+
);
|
|
4688
|
+
}
|
|
4689
|
+
function ChoiceView({ choice, picked, onPick }) {
|
|
4690
|
+
const [adding, setAdding] = d2(false);
|
|
4691
|
+
const [typed, setTyped] = d2("");
|
|
4692
|
+
const known = new Set(choice.options.map((option) => option.value));
|
|
4693
|
+
const own = picked.filter((value) => !known.has(value));
|
|
4694
|
+
const toggle = (value) => {
|
|
4695
|
+
if (!choice.multiple) return onPick([value]);
|
|
4696
|
+
onPick(picked.includes(value) ? picked.filter((v4) => v4 !== value) : [...picked, value]);
|
|
4697
|
+
};
|
|
4698
|
+
const add = () => {
|
|
4699
|
+
const value = typed.trim();
|
|
4700
|
+
if (value) onPick(choice.multiple ? [...picked.filter((v4) => v4 !== value), value] : [value]);
|
|
4701
|
+
setTyped("");
|
|
4702
|
+
setAdding(false);
|
|
4703
|
+
};
|
|
4704
|
+
const adder = choice.custom ? adding ? el(
|
|
4705
|
+
"div",
|
|
4706
|
+
{ class: "onboarding__add" },
|
|
4707
|
+
el("input", {
|
|
4708
|
+
type: "text",
|
|
4709
|
+
value: typed,
|
|
4710
|
+
placeholder: choice.custom,
|
|
4711
|
+
"aria-label": choice.custom,
|
|
4712
|
+
ref: (input) => input?.focus(),
|
|
4713
|
+
onInput: (event) => setTyped(event.currentTarget.value),
|
|
4714
|
+
onKeyDown: (event) => {
|
|
4715
|
+
if (event.key === "Enter") {
|
|
4716
|
+
event.preventDefault();
|
|
4717
|
+
add();
|
|
4718
|
+
}
|
|
4719
|
+
if (event.key === "Escape") setAdding(false);
|
|
4720
|
+
}
|
|
4721
|
+
}),
|
|
4722
|
+
el("button", { type: "button", class: "button", onClick: add }, "Add")
|
|
4723
|
+
) : el("button", { type: "button", class: "onboarding__add-button", onClick: () => setAdding(true) }, choice.multiple ? choice.custom : "Other\u2026") : null;
|
|
4724
|
+
const label = choice.label ? el("legend", { class: "onboarding__legend" }, choice.label) : null;
|
|
4725
|
+
const rows = choice.multiple || choice.options.some((option) => option.detail);
|
|
4726
|
+
if (!rows) {
|
|
4727
|
+
return el(
|
|
4728
|
+
"fieldset",
|
|
4729
|
+
{ class: "onboarding__choice" },
|
|
4730
|
+
label,
|
|
4731
|
+
el(
|
|
4732
|
+
"div",
|
|
4733
|
+
{ class: "onboarding__pills", role: "radiogroup" },
|
|
4734
|
+
[...choice.options, ...own.map((value) => ({ value, label: value, detail: null, group: null }))].map(
|
|
4735
|
+
(option) => el(
|
|
4736
|
+
"button",
|
|
4737
|
+
{
|
|
4738
|
+
type: "button",
|
|
4739
|
+
key: option.value,
|
|
4740
|
+
role: "radio",
|
|
4741
|
+
class: "onboarding__pill",
|
|
4742
|
+
"aria-checked": String(picked.includes(option.value)),
|
|
4743
|
+
onClick: () => toggle(option.value)
|
|
4744
|
+
},
|
|
4745
|
+
option.label
|
|
4746
|
+
)
|
|
4747
|
+
),
|
|
4748
|
+
adder
|
|
4749
|
+
)
|
|
4750
|
+
);
|
|
4751
|
+
}
|
|
4752
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4753
|
+
for (const option of choice.options) groups.set(option.group, [...groups.get(option.group) ?? [], option]);
|
|
4754
|
+
if (own.length) groups.set("Added by you", own.map((value) => ({ value, label: value, detail: null, group: "Added by you" })));
|
|
4755
|
+
return el(
|
|
4756
|
+
"fieldset",
|
|
4757
|
+
{ class: "onboarding__choice" },
|
|
4758
|
+
label,
|
|
4759
|
+
el(
|
|
4760
|
+
"div",
|
|
4761
|
+
{ class: "onboarding__list" },
|
|
4762
|
+
[...groups].map(
|
|
4763
|
+
([group, options]) => el(
|
|
4764
|
+
"div",
|
|
4765
|
+
{ class: "onboarding__group", key: group ?? "" },
|
|
4766
|
+
group ? el("p", { class: "onboarding__group-name" }, group) : null,
|
|
4767
|
+
options.map(
|
|
4768
|
+
(option) => el(
|
|
4769
|
+
"label",
|
|
4770
|
+
{ class: "onboarding__option", key: option.value },
|
|
4771
|
+
el("input", {
|
|
4772
|
+
type: choice.multiple ? "checkbox" : "radio",
|
|
4773
|
+
name: choice.id,
|
|
4774
|
+
checked: picked.includes(option.value),
|
|
4775
|
+
onChange: () => toggle(option.value)
|
|
4776
|
+
}),
|
|
4777
|
+
el("span", { class: "onboarding__box", "data-kind": choice.multiple ? "check" : "radio", "aria-hidden": "true" }),
|
|
4778
|
+
el("span", { class: "onboarding__option-text" }, option.label, option.detail ? el("small", null, option.detail) : null)
|
|
4779
|
+
)
|
|
4780
|
+
)
|
|
4781
|
+
)
|
|
4782
|
+
),
|
|
4783
|
+
choice.options.length === 0 && own.length === 0 && !choice.custom ? el("p", { class: "onboarding__none" }, "Nothing to choose from.") : null,
|
|
4784
|
+
adder ? el("div", { class: "onboarding__adder" }, adder) : null
|
|
4785
|
+
)
|
|
4786
|
+
);
|
|
4787
|
+
}
|
|
4788
|
+
function fieldView(field, value, onInput) {
|
|
4789
|
+
return el(
|
|
4790
|
+
"div",
|
|
4791
|
+
{ class: "onboarding__field", key: field.id },
|
|
4792
|
+
el(
|
|
4793
|
+
"label",
|
|
4794
|
+
null,
|
|
4795
|
+
el("span", { class: "onboarding__field-label" }, field.label),
|
|
4796
|
+
el("input", {
|
|
4797
|
+
type: "text",
|
|
4798
|
+
value,
|
|
4799
|
+
placeholder: field.placeholder ?? void 0,
|
|
4800
|
+
onInput: (event) => onInput(event.currentTarget.value)
|
|
4801
|
+
})
|
|
4802
|
+
),
|
|
4803
|
+
field.help ? el("small", null, field.help) : null,
|
|
4804
|
+
// Offered, never filled in: a click puts one in the field, to edit or keep.
|
|
4805
|
+
field.suggestions?.length ? el(
|
|
4806
|
+
"ul",
|
|
4807
|
+
{ class: "onboarding__suggestions" },
|
|
4808
|
+
field.suggestions.map(
|
|
4809
|
+
(suggestion) => el(
|
|
4810
|
+
"li",
|
|
4811
|
+
{ key: suggestion.text },
|
|
4812
|
+
el(
|
|
4813
|
+
"button",
|
|
4814
|
+
{ type: "button", "aria-pressed": String(value === suggestion.text), onClick: () => onInput(suggestion.text) },
|
|
4815
|
+
el("span", null, suggestion.text),
|
|
4816
|
+
suggestion.detail ? el("small", null, suggestion.detail) : null
|
|
4817
|
+
)
|
|
4818
|
+
)
|
|
4819
|
+
)
|
|
4820
|
+
) : null
|
|
4821
|
+
);
|
|
4822
|
+
}
|
|
4823
|
+
function FinishCard({ flow, ui: ui2, handlers: handlers2 }) {
|
|
4824
|
+
const writes = flow.steps.find((step) => step.id === "cli")?.status === "answered";
|
|
4825
|
+
const busy = ui2.setupBusy.value !== null;
|
|
4826
|
+
const error = (ui2.setupError.value?.step === "finish" ? ui2.setupError.value.message : null) ?? flow.error;
|
|
4827
|
+
const sources = ui2.setupSources.value;
|
|
4828
|
+
return el(
|
|
4829
|
+
"section",
|
|
4830
|
+
{ class: "onboarding__card onboarding__finish", "data-current": "true", "aria-labelledby": "setup-finish" },
|
|
4831
|
+
el("span", { class: "onboarding__dot onboarding__dot--open", "aria-hidden": "true" }),
|
|
4832
|
+
el("h2", { id: "setup-finish", tabIndex: -1 }, writes ? "Ready for your first brief" : "Ready to finish"),
|
|
4833
|
+
el(
|
|
4834
|
+
"p",
|
|
4835
|
+
{ class: "onboarding__why" },
|
|
4836
|
+
writes ? "It takes a few minutes, and you can watch it work. After that it runs by itself, and everything here can be changed in Settings." : "Without an agent there is no brief to write yet. Choose one in Settings when you are ready; the boards work meanwhile."
|
|
4837
|
+
),
|
|
4838
|
+
el(
|
|
4839
|
+
"details",
|
|
4840
|
+
{
|
|
4841
|
+
class: "onboarding__preview",
|
|
4842
|
+
onToggle: (event) => {
|
|
4843
|
+
if (event.currentTarget.open && sources === null) handlers2.loadSetupSources();
|
|
4844
|
+
}
|
|
4845
|
+
},
|
|
4846
|
+
el("summary", null, "What the morning agent reads each morning"),
|
|
4847
|
+
sources === null || sources === "loading" ? el("p", null, "Loading\u2026") : "error" in sources ? el("p", null, `Could not show it: ${sources.error}`) : sources.kept ? el("p", null, "Your source list is already written, and setup leaves it as it is.") : sources.text ? el("pre", null, sources.text) : el("p", null, "Nothing yet: the agent briefs from what it can reach, and says what it missed.")
|
|
4848
|
+
),
|
|
4849
|
+
error ? el("p", { class: "onboarding__error", role: "alert" }, error) : null,
|
|
4850
|
+
el(
|
|
4851
|
+
"div",
|
|
4852
|
+
{ class: "onboarding__actions" },
|
|
4853
|
+
el(
|
|
4854
|
+
"button",
|
|
4855
|
+
{ type: "button", class: "button button--primary", disabled: busy, onClick: () => handlers2.finishSetup() },
|
|
4856
|
+
ui2.setupBusy.value === "finish" ? "Saving\u2026" : writes ? "Write my first brief" : "Finish setup"
|
|
4857
|
+
)
|
|
4858
|
+
)
|
|
4309
4859
|
);
|
|
4310
4860
|
}
|
|
4311
4861
|
|
|
@@ -4383,7 +4933,7 @@ function objectiveCard(state2, ui2, handlers2) {
|
|
|
4383
4933
|
)
|
|
4384
4934
|
);
|
|
4385
4935
|
}
|
|
4386
|
-
function objectiveForm(objective, blocker, handlers2
|
|
4936
|
+
function objectiveForm(objective, blocker, handlers2) {
|
|
4387
4937
|
return el(
|
|
4388
4938
|
"form",
|
|
4389
4939
|
{
|
|
@@ -4414,7 +4964,7 @@ ${blocker}`,
|
|
|
4414
4964
|
el("span", { class: "field__label" }, "Blocked on (optional)"),
|
|
4415
4965
|
el("input", { class: "field__input", name: "blocker", defaultValue: blocker, placeholder: "What is in the way today, if anything", autocomplete: "off" })
|
|
4416
4966
|
),
|
|
4417
|
-
el("div", { class: "objective-form__actions" }, el("button", { type: "submit", class: "button button--primary" },
|
|
4967
|
+
el("div", { class: "objective-form__actions" }, el("button", { type: "submit", class: "button button--primary" }, "Save objective"))
|
|
4418
4968
|
);
|
|
4419
4969
|
}
|
|
4420
4970
|
function sourcesCard(state2, ui2, handlers2) {
|
|
@@ -4498,13 +5048,13 @@ function settingField(setting, draft, handlers2) {
|
|
|
4498
5048
|
};
|
|
4499
5049
|
let input;
|
|
4500
5050
|
if (setting.kind === "choice" || setting.kind === "flag") {
|
|
4501
|
-
const
|
|
5051
|
+
const choices = setting.kind === "flag" ? [{ value: "on", label: "On" }, { value: "off", label: "Off" }] : setting.choices ?? [];
|
|
4502
5052
|
const current = setting.kind === "flag" ? flagValue(value) : value;
|
|
4503
5053
|
input = el(
|
|
4504
5054
|
"select",
|
|
4505
5055
|
{ class: "field__input", id, disabled: locked, value: current, onChange },
|
|
4506
5056
|
el("option", { value: "" }, `Default: ${setting.fallback}`),
|
|
4507
|
-
|
|
5057
|
+
choices.map((choice) => el("option", { value: choice.value, key: choice.value }, choice.label))
|
|
4508
5058
|
);
|
|
4509
5059
|
} else {
|
|
4510
5060
|
input = el("input", {
|
|
@@ -4545,7 +5095,7 @@ function calendarPicker(state2, ui2, handlers2, settings) {
|
|
|
4545
5095
|
const setting = settings.find((entry) => entry.key === "DAILY_FOCUS_CALENDARS");
|
|
4546
5096
|
if (!setting || setting.source === "environment") return null;
|
|
4547
5097
|
const chosen = shownValue(setting, ui2.settingsDraft.value).split(",").map((name) => name.trim()).filter(Boolean);
|
|
4548
|
-
const
|
|
5098
|
+
const listed2 = ui2.calendars.value;
|
|
4549
5099
|
const toggle = (title, on) => {
|
|
4550
5100
|
const others = chosen.filter((name) => name.toLowerCase() !== title.toLowerCase());
|
|
4551
5101
|
const next = on ? [...others, title] : others;
|
|
@@ -4555,11 +5105,11 @@ function calendarPicker(state2, ui2, handlers2, settings) {
|
|
|
4555
5105
|
"div",
|
|
4556
5106
|
{ class: "calendar-picker" },
|
|
4557
5107
|
el("button", { type: "button", class: "button", onClick: () => handlers2.listCalendars() }, "List the calendars on this Mac"),
|
|
4558
|
-
typeof
|
|
4559
|
-
Array.isArray(
|
|
5108
|
+
typeof listed2 === "string" ? el("p", { class: "settings-card__warning" }, listed2) : null,
|
|
5109
|
+
Array.isArray(listed2) ? el(
|
|
4560
5110
|
"ul",
|
|
4561
5111
|
{ class: "calendar-picker__list" },
|
|
4562
|
-
|
|
5112
|
+
listed2.map(
|
|
4563
5113
|
(entry) => el(
|
|
4564
5114
|
"li",
|
|
4565
5115
|
{ key: `${entry.title}
|
|
@@ -4602,255 +5152,58 @@ function saveBar(ui2, handlers2) {
|
|
|
4602
5152
|
}
|
|
4603
5153
|
|
|
4604
5154
|
// client/setup.ts
|
|
4605
|
-
|
|
4606
|
-
function SetupCard({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
4607
|
-
const setup = state2.setup;
|
|
5155
|
+
function FirstBriefCard({ state: state2, handlers: handlers2 }) {
|
|
4608
5156
|
const agent = state2.agentRun;
|
|
4609
|
-
const
|
|
4610
|
-
const
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
el(
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
),
|
|
4634
|
-
// A choice, so neither answer is drawn as the one to pick.
|
|
4635
|
-
choices([
|
|
4636
|
-
["Work", () => handlers2.chooseSettings({ DAILY_FOCUS_PROFILE: "work" })],
|
|
4637
|
-
["Personal life", () => handlers2.chooseSettings({ DAILY_FOCUS_PROFILE: "personal" })]
|
|
4638
|
-
])
|
|
4639
|
-
]
|
|
4640
|
-
),
|
|
4641
|
-
step(
|
|
4642
|
-
Boolean(state2.focus?.objective),
|
|
4643
|
-
"What are you trying to achieve?",
|
|
4644
|
-
state2.focus?.objective ? el("p", null, state2.focus.objective) : [
|
|
4645
|
-
el("p", null, "The agent ranks the day against it. Leave it for now if nothing stands out."),
|
|
4646
|
-
objectiveForm("", "", handlers2, "Set objective")
|
|
4647
|
-
]
|
|
4648
|
-
),
|
|
4649
|
-
step(
|
|
4650
|
-
agent.cli !== null,
|
|
4651
|
-
"Which agent writes the brief?",
|
|
4652
|
-
agent.cli ? [
|
|
4653
|
-
el(
|
|
4654
|
-
"p",
|
|
4655
|
-
null,
|
|
4656
|
-
`${CLI_LABEL[agent.cli]}. ` + (agent.schedule ? `After the first brief, it runs by itself each scheduled morning at ${agent.schedule.at}. ` : "It runs when you press refresh. ") + "It has to be logged in; the first run says if it is not."
|
|
4657
|
-
),
|
|
4658
|
-
agentAdvanced(agent.cli, ui2, handlers2)
|
|
4659
|
-
] : agentChoice(state2, handlers2)
|
|
4660
|
-
),
|
|
4661
|
-
step(setup.sources.exists && setup.sources.placeholders === 0, "Who are you, and where should it look?", sourcesStep(state2, ui2, handlers2)),
|
|
4662
|
-
step(null, "What else should the dashboard show?", boards(state2, handlers2)),
|
|
4663
|
-
step(
|
|
4664
|
-
null,
|
|
4665
|
-
"Your first brief",
|
|
4666
|
-
[
|
|
4667
|
-
el(
|
|
4668
|
-
"p",
|
|
4669
|
-
null,
|
|
4670
|
-
running ? "The morning agent is writing it now. Click the banner above to watch." : 'The morning agent writes it now, which takes a few minutes, and from then on by itself every scheduled morning. When it is done, click "updated \u2026 ago" at the top to read its report: what it found, and anything it could not reach.' + (setup.sources.exists ? "" : " Without a source list it briefs from what it can reach on its own, and says what it missed.")
|
|
4671
|
-
),
|
|
4672
|
-
placeholders ? el("p", { class: "setup__note setup__note--todo" }, "Replace the source list's placeholders first, or the agent goes looking for them.") : null,
|
|
4673
|
-
el(
|
|
4674
|
-
"button",
|
|
4675
|
-
{
|
|
4676
|
-
type: "button",
|
|
4677
|
-
class: "button button--primary",
|
|
4678
|
-
disabled: !agent.enabled || running || state2.restart === "waiting" || placeholders,
|
|
4679
|
-
title: !agent.enabled ? "Choose the agent first" : placeholders ? "Replace the source list's placeholders first" : void 0,
|
|
4680
|
-
onClick: () => handlers2.runAgent()
|
|
4681
|
-
},
|
|
4682
|
-
running ? "Running\u2026" : "Run the morning agent now"
|
|
4683
|
-
)
|
|
4684
|
-
]
|
|
4685
|
-
)
|
|
4686
|
-
)
|
|
4687
|
-
);
|
|
4688
|
-
}
|
|
4689
|
-
function step(done, title, body) {
|
|
4690
|
-
return el(
|
|
4691
|
-
"li",
|
|
4692
|
-
{ class: "setup__step", "data-done": done === null ? void 0 : String(done) },
|
|
4693
|
-
el(
|
|
4694
|
-
"span",
|
|
4695
|
-
{ class: "setup__mark", "aria-label": done === null ? void 0 : done ? "Done" : "To do" },
|
|
4696
|
-
done === null ? "\xB7" : done ? "\u2713" : "\u25CB"
|
|
4697
|
-
),
|
|
4698
|
-
el("div", { class: "setup__body" }, el("h3", { class: "setup__step-title" }, title), body)
|
|
4699
|
-
);
|
|
4700
|
-
}
|
|
4701
|
-
function choices(options) {
|
|
4702
|
-
return el(
|
|
4703
|
-
"div",
|
|
4704
|
-
{ class: "setup__choices" },
|
|
4705
|
-
options.map(([label, onClick, title]) => el("button", { type: "button", class: "button", key: label, title, onClick }, label))
|
|
4706
|
-
);
|
|
4707
|
-
}
|
|
4708
|
-
function agentChoice(state2, handlers2) {
|
|
4709
|
-
const found = ["claude", "codex"].filter((cli) => state2.setup.clis[cli]);
|
|
4710
|
-
if (found.length === 0) {
|
|
4711
|
-
return [
|
|
4712
|
-
el(
|
|
4713
|
-
"p",
|
|
4714
|
-
null,
|
|
4715
|
-
"The brief is written by a coding-agent CLI you already use. Install ",
|
|
4716
|
-
el("a", { href: "https://code.claude.com/docs", target: "_blank", rel: "noopener" }, "Claude Code"),
|
|
4717
|
-
" or the ",
|
|
4718
|
-
el("a", { href: "https://developers.openai.com/codex/cli", target: "_blank", rel: "noopener" }, "Codex CLI"),
|
|
4719
|
-
", log in, and restart the dashboard; it looks for both each time it starts."
|
|
4720
|
-
)
|
|
5157
|
+
const last = agent.last;
|
|
5158
|
+
const running = last?.status === "running";
|
|
5159
|
+
const restarting = state2.restart === "waiting";
|
|
5160
|
+
let title;
|
|
5161
|
+
let text;
|
|
5162
|
+
let actions;
|
|
5163
|
+
if (restarting) {
|
|
5164
|
+
title = "Saving your settings";
|
|
5165
|
+
text = "The dashboard restarts to apply them, then starts on your first brief.";
|
|
5166
|
+
actions = [];
|
|
5167
|
+
} else if (!agent.enabled) {
|
|
5168
|
+
title = "Choose who writes your brief";
|
|
5169
|
+
text = "Each morning a coding-agent CLI you already use writes it: Claude Code or Codex. The boards work meanwhile.";
|
|
5170
|
+
actions = [el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.openSettings("agent") }, "Choose in Settings")];
|
|
5171
|
+
} else if (running) {
|
|
5172
|
+
title = "Writing your first brief";
|
|
5173
|
+
text = "The morning agent is reading your mail, calendar and the rest. It takes a few minutes; the brief appears here when it is done.";
|
|
5174
|
+
actions = [el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Watch it work")];
|
|
5175
|
+
} else if (last && last.status !== "done") {
|
|
5176
|
+
title = "Your first brief didn\u2019t finish";
|
|
5177
|
+
text = last.status === "aborted" ? "It was stopped before it wrote a brief." : last.error ? `The run failed: ${last.error}.` : "The run stopped before it wrote a brief.";
|
|
5178
|
+
actions = [
|
|
5179
|
+
el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Try again"),
|
|
5180
|
+
el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Read its report")
|
|
4721
5181
|
];
|
|
5182
|
+
} else if (last) {
|
|
5183
|
+
title = "Your first brief didn\u2019t arrive";
|
|
5184
|
+
text = "The run finished without a brief this dashboard could read. Its report says why.";
|
|
5185
|
+
actions = [
|
|
5186
|
+
el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Run it again"),
|
|
5187
|
+
el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Read its report")
|
|
5188
|
+
];
|
|
5189
|
+
} else {
|
|
5190
|
+
title = "Write your first brief";
|
|
5191
|
+
text = "It takes a few minutes. After that it runs by itself each scheduled morning.";
|
|
5192
|
+
actions = [el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Write it now")];
|
|
4722
5193
|
}
|
|
4723
|
-
return [
|
|
4724
|
-
el("p", null, "The dashboard runs a coding-agent CLI you already use, headless, each morning. Found on this machine:"),
|
|
4725
|
-
choices(
|
|
4726
|
-
found.map(
|
|
4727
|
-
(cli) => [`Use ${CLI_LABEL[cli]}`, () => handlers2.chooseSettings({ DAILY_FOCUS_AGENT: cli }), state2.setup.clis[cli] ?? void 0]
|
|
4728
|
-
)
|
|
4729
|
-
)
|
|
4730
|
-
];
|
|
4731
|
-
}
|
|
4732
|
-
var AGENT_ADVANCED = "setup:agent-advanced";
|
|
4733
|
-
function agentAdvanced(cli, ui2, handlers2) {
|
|
4734
|
-
const keys = ["DAILY_FOCUS_AGENT_MODEL", "DAILY_FOCUS_AGENT_EFFORT", ...cli === "claude" ? ["DAILY_FOCUS_AGENT_TOOLS"] : []];
|
|
4735
|
-
const page = ui2.settings.value;
|
|
4736
|
-
const draft = ui2.settingsDraft.value;
|
|
4737
|
-
const changed = keys.some((key) => draft.has(key));
|
|
4738
|
-
const error = changed ? ui2.settingsError.value : null;
|
|
4739
|
-
return el(
|
|
4740
|
-
"details",
|
|
4741
|
-
{
|
|
4742
|
-
class: "setup__advanced",
|
|
4743
|
-
open: ui2.openDrawers.value.has(AGENT_ADVANCED),
|
|
4744
|
-
onToggle: (event) => {
|
|
4745
|
-
const open = event.currentTarget.open;
|
|
4746
|
-
if (open && !ui2.settings.peek()) handlers2.loadSettings();
|
|
4747
|
-
handlers2.toggleDrawer(AGENT_ADVANCED, open);
|
|
4748
|
-
}
|
|
4749
|
-
},
|
|
4750
|
-
el("summary", null, cli === "claude" ? "Advanced: model, effort and tools" : "Advanced: model and effort"),
|
|
4751
|
-
page ? [
|
|
4752
|
-
...keys.flatMap((key) => {
|
|
4753
|
-
const setting = page.settings.find((entry) => entry.key === key);
|
|
4754
|
-
return setting ? [settingField(setting, draft, handlers2)] : [];
|
|
4755
|
-
}),
|
|
4756
|
-
error ? el("p", { class: "setup__note setup__note--todo", role: "alert" }, error) : null,
|
|
4757
|
-
el(
|
|
4758
|
-
"div",
|
|
4759
|
-
{ class: "setup__advanced-actions" },
|
|
4760
|
-
el(
|
|
4761
|
-
"button",
|
|
4762
|
-
{ type: "button", class: "button", disabled: !changed || ui2.settingsSaving.value, onClick: () => handlers2.saveSettings(keys) },
|
|
4763
|
-
ui2.settingsSaving.value ? "Saving\u2026" : "Save"
|
|
4764
|
-
)
|
|
4765
|
-
)
|
|
4766
|
-
] : el("p", null, "Loading the settings\u2026")
|
|
4767
|
-
);
|
|
4768
|
-
}
|
|
4769
|
-
var SOURCES_EDITOR = "setup:sources";
|
|
4770
|
-
function sourcesStep(state2, ui2, handlers2) {
|
|
4771
|
-
const sources = state2.setup.sources;
|
|
4772
|
-
const open = ui2.openDrawers.value.has(SOURCES_EDITOR);
|
|
4773
|
-
const toggle = () => {
|
|
4774
|
-
if (!open && !ui2.texts.value.sources) handlers2.loadText("sources");
|
|
4775
|
-
handlers2.toggleDrawer(SOURCES_EDITOR, !open);
|
|
4776
|
-
};
|
|
4777
|
-
return [
|
|
4778
|
-
el(
|
|
4779
|
-
"p",
|
|
4780
|
-
null,
|
|
4781
|
-
"A short list the agent reads every morning. First who you are: your name as meeting notes write it, your email and your GitHub login, so it can tell your to-dos from everyone else's. Then where to look: which calendars, mailboxes and documents are yours."
|
|
4782
|
-
),
|
|
4783
|
-
sources.exists ? el(
|
|
4784
|
-
"p",
|
|
4785
|
-
{ class: sources.placeholders > 0 ? "setup__note setup__note--todo" : "setup__note" },
|
|
4786
|
-
sources.placeholders > 0 ? `It still has ${sources.placeholders} of the template's placeholders. Replace them, or delete what doesn't apply.` : "Written."
|
|
4787
|
-
) : el("p", { class: "setup__note" }, "It starts from a template, with what gh and git know about you already filled in."),
|
|
4788
|
-
el(
|
|
4789
|
-
"button",
|
|
4790
|
-
{ type: "button", class: "button", "aria-expanded": String(open), onClick: toggle },
|
|
4791
|
-
open ? "Close" : sources.exists ? "Edit it" : "Write it"
|
|
4792
|
-
),
|
|
4793
|
-
open ? el("div", { class: "setup__editor" }, textEditor("sources", ui2.texts.value.sources, handlers2, "sources.md")) : null
|
|
4794
|
-
];
|
|
4795
|
-
}
|
|
4796
|
-
var HEALTH_MARK = { ok: "\u2713", checking: "\u2026", problem: "!", todo: "\u25CB" };
|
|
4797
|
-
var HEALTH_LABEL = {
|
|
4798
|
-
ok: "Connected",
|
|
4799
|
-
checking: "Checking",
|
|
4800
|
-
problem: "Not working",
|
|
4801
|
-
todo: "To do"
|
|
4802
|
-
};
|
|
4803
|
-
function boardHealth(board, via, service) {
|
|
4804
|
-
if (board.reason) return ["problem", board.reason.message];
|
|
4805
|
-
if (board.fetchedAt) return ["ok", `Connected, through ${via}.`];
|
|
4806
|
-
return ["checking", `Checking ${service} through ${via}\u2026`];
|
|
4807
|
-
}
|
|
4808
|
-
function boards(state2, handlers2) {
|
|
4809
|
-
const rows = [];
|
|
4810
|
-
const off = (key, label) => el("button", { type: "button", class: "button", onClick: () => handlers2.chooseSettings({ [key]: "off" }) }, `Turn off ${label}`);
|
|
4811
|
-
if (state2.board.enabled) {
|
|
4812
|
-
rows.push(boardRow("Pull requests", ...boardHealth(state2.board, "gh", "GitHub"), off("DAILY_FOCUS_GITHUB", "the pull request board")));
|
|
4813
|
-
}
|
|
4814
|
-
if (state2.tickets.enabled) {
|
|
4815
|
-
rows.push(boardRow("Jira tickets", ...boardHealth(state2.tickets, "acli", "Jira"), off("DAILY_FOCUS_JIRA", "the Jira board")));
|
|
4816
|
-
}
|
|
4817
|
-
const calendar = state2.setup.calendar;
|
|
4818
|
-
if (calendar.supported && state2.agendaSource) {
|
|
4819
|
-
const live = state2.agendaSource.live;
|
|
4820
|
-
const [health, text] = !calendar.built ? ["todo", `Needs the calendar helper: run ${calendar.buildCommand} once. Until then the agenda uses the brief.`] : calendar.chosen === 0 ? ["todo", "No calendars chosen yet. Choose them, and the agenda follows them live."] : live ? ["ok", `Connected, reading ${calendar.chosen} ${calendar.chosen === 1 ? "calendar" : "calendars"} from Calendar.app.`] : state2.agendaSource.problem ? ["problem", state2.agendaSource.problem] : ["checking", "Checking Calendar.app\u2026"];
|
|
4821
|
-
rows.push(
|
|
4822
|
-
boardRow(
|
|
4823
|
-
"Calendar",
|
|
4824
|
-
health,
|
|
4825
|
-
text,
|
|
4826
|
-
calendar.built && calendar.chosen === 0 ? el("button", { type: "button", class: "button", onClick: () => handlers2.openSettings("calendar") }, "Choose calendars") : off("DAILY_FOCUS_CALENDAR", "the live agenda")
|
|
4827
|
-
)
|
|
4828
|
-
);
|
|
4829
|
-
}
|
|
4830
|
-
if (rows.length === 0) return el("p", null, "Nothing else is switched on. The boards can be turned on in Settings.");
|
|
4831
|
-
return el("ul", { class: "setup__boards" }, rows);
|
|
4832
|
-
}
|
|
4833
|
-
function boardRow(name, health, text, action) {
|
|
4834
5194
|
return el(
|
|
4835
|
-
"
|
|
4836
|
-
{ class: "
|
|
4837
|
-
el("
|
|
4838
|
-
el("
|
|
4839
|
-
el("
|
|
4840
|
-
action
|
|
5195
|
+
"section",
|
|
5196
|
+
{ class: "first-brief", id: "first-brief", "aria-labelledby": "first-brief-title", "data-state": running ? "running" : "idle" },
|
|
5197
|
+
el("h2", { class: "first-brief__title", id: "first-brief-title" }, title),
|
|
5198
|
+
el("p", { class: "first-brief__text" }, text),
|
|
5199
|
+
actions.length ? el("div", { class: "first-brief__actions" }, actions) : null
|
|
4841
5200
|
);
|
|
4842
5201
|
}
|
|
4843
5202
|
|
|
4844
5203
|
// client/today.ts
|
|
4845
5204
|
function TodayView({ state: state2, ui: ui2, handlers: handlers2 }) {
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
{ class: "focus-bar", id: "focus-bar", hidden: !ui2.focusMode.value },
|
|
4849
|
-
"Focus mode \u2014 everything but the top item is hidden. ",
|
|
4850
|
-
el("button", { type: "button", class: "button", id: "focus-exit", onClick: () => handlers2.setFocusMode(false) }, "Show everything")
|
|
4851
|
-
);
|
|
4852
|
-
if (state2.setup?.needed) return [focusBar, k(SetupCard, { state: state2, ui: ui2, handlers: handlers2 })];
|
|
4853
|
-
return [focusBar, renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2), renderSections(state2, ui2, handlers2)];
|
|
5205
|
+
if (state2.setup?.needed) return [k(FirstBriefCard, { state: state2, handlers: handlers2 })];
|
|
5206
|
+
return [renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2), renderSections(state2, ui2, handlers2)];
|
|
4854
5207
|
}
|
|
4855
5208
|
function renderObjective(state2, handlers2 = null) {
|
|
4856
5209
|
const focus = state2.focus;
|
|
@@ -4906,15 +5259,15 @@ function focusTimeStat(agenda, now) {
|
|
|
4906
5259
|
const tone = minutes === 0 ? "critical" : minutes < 60 ? "critical" : null;
|
|
4907
5260
|
return stat("Focus time left", minutes === 0 ? "None" : formatDuration(minutes), tone, `Day ends ${agenda.dayEnd}`);
|
|
4908
5261
|
}
|
|
4909
|
-
function objectiveStat(
|
|
4910
|
-
if (!
|
|
4911
|
-
if (
|
|
5262
|
+
function objectiveStat(progress2) {
|
|
5263
|
+
if (!progress2) return null;
|
|
5264
|
+
if (progress2.workingDaysSince === null) {
|
|
4912
5265
|
return stat("Since objective progress", "\u2014", null, "Nothing recorded yet");
|
|
4913
5266
|
}
|
|
4914
|
-
const days =
|
|
5267
|
+
const days = progress2.workingDaysSince;
|
|
4915
5268
|
const tone = days === 0 ? "good" : days >= 3 ? "critical" : null;
|
|
4916
5269
|
const value = days === 0 ? "Today" : days === 1 ? "1 day" : `${days} days`;
|
|
4917
|
-
return stat("Since objective progress", value, tone,
|
|
5270
|
+
return stat("Since objective progress", value, tone, progress2.lastTitle);
|
|
4918
5271
|
}
|
|
4919
5272
|
function renderHeadline(state2) {
|
|
4920
5273
|
const headline = state2.brief.headline;
|
|
@@ -4969,11 +5322,11 @@ function App({ ui: ui2, handlers: handlers2 }) {
|
|
|
4969
5322
|
const view = ui2.view.value;
|
|
4970
5323
|
const run = current ? detailRun(current, ui2) : null;
|
|
4971
5324
|
const row = current && !run ? detailRow(current, ui2) : null;
|
|
5325
|
+
if (current?.onboarding) return [k(OnboardingView, { state: current, ui: ui2, handlers: handlers2 }), k(Toast, { ui: ui2, handlers: handlers2 })];
|
|
4972
5326
|
return [
|
|
4973
5327
|
el("a", { class: "skip-link", href: "#main" }, "Skip to content"),
|
|
4974
5328
|
k(Header, { state: current, ui: ui2, handlers: handlers2 }),
|
|
4975
5329
|
k(Tabs, { state: current, ui: ui2, handlers: handlers2 }),
|
|
4976
|
-
current ? k(Timer, { state: current, ui: ui2, handlers: handlers2 }) : el("div", { class: "timer", id: "timer", hidden: true }),
|
|
4977
5330
|
el("div", { id: "banners", class: "banners" }, current ? renderBanners(current, ui2, handlers2) : null),
|
|
4978
5331
|
el(
|
|
4979
5332
|
"main",
|
|
@@ -5091,11 +5444,6 @@ function installKeyboard(handlers2) {
|
|
|
5091
5444
|
event.preventDefault();
|
|
5092
5445
|
moveSelection(-1);
|
|
5093
5446
|
return;
|
|
5094
|
-
case "f":
|
|
5095
|
-
if (ui.view.value !== "today") return;
|
|
5096
|
-
event.preventDefault();
|
|
5097
|
-
handlers2.setFocusMode(!ui.focusMode.value);
|
|
5098
|
-
return;
|
|
5099
5447
|
case "1":
|
|
5100
5448
|
event.preventDefault();
|
|
5101
5449
|
handlers2.setView("today");
|
|
@@ -5138,13 +5486,12 @@ function installKeyboard(handlers2) {
|
|
|
5138
5486
|
}
|
|
5139
5487
|
const item = selectedItem();
|
|
5140
5488
|
if (!item) return;
|
|
5141
|
-
const
|
|
5489
|
+
const boardRow = ui.view.value !== "today";
|
|
5142
5490
|
const status = "status" in item ? item.status : null;
|
|
5143
|
-
const kind = "kind" in item ? item.kind : null;
|
|
5144
5491
|
switch (event.key) {
|
|
5145
5492
|
case "e":
|
|
5146
5493
|
event.preventDefault();
|
|
5147
|
-
if (!
|
|
5494
|
+
if (!boardRow && status === "open") handlers2.onAction(item.id, "done");
|
|
5148
5495
|
break;
|
|
5149
5496
|
case "s":
|
|
5150
5497
|
event.preventDefault();
|
|
@@ -5152,7 +5499,7 @@ function installKeyboard(handlers2) {
|
|
|
5152
5499
|
break;
|
|
5153
5500
|
case "x":
|
|
5154
5501
|
event.preventDefault();
|
|
5155
|
-
if (!
|
|
5502
|
+
if (!boardRow && status === "open") handlers2.onAction(item.id, "dismiss");
|
|
5156
5503
|
break;
|
|
5157
5504
|
case "n":
|
|
5158
5505
|
event.preventDefault();
|
|
@@ -5163,12 +5510,6 @@ function installKeyboard(handlers2) {
|
|
|
5163
5510
|
event.preventDefault();
|
|
5164
5511
|
handlers2.openDetail(item.id, "assistant");
|
|
5165
5512
|
break;
|
|
5166
|
-
case "p":
|
|
5167
|
-
event.preventDefault();
|
|
5168
|
-
if (boardRow2) break;
|
|
5169
|
-
if (state.value?.session.active?.id === item.id) handlers2.stopSession();
|
|
5170
|
-
else if (status === "open" && kind === "task") handlers2.startSession(item.id);
|
|
5171
|
-
break;
|
|
5172
5513
|
case "o":
|
|
5173
5514
|
if (item.url) {
|
|
5174
5515
|
event.preventDefault();
|
|
@@ -5189,14 +5530,11 @@ j3(() => {
|
|
|
5189
5530
|
document.body.dataset.view = ui.view.value;
|
|
5190
5531
|
});
|
|
5191
5532
|
j3(() => {
|
|
5192
|
-
document.body.dataset.
|
|
5533
|
+
document.body.dataset.onboarding = String(Boolean(state.value?.onboarding));
|
|
5193
5534
|
});
|
|
5194
5535
|
j3(() => {
|
|
5195
5536
|
document.body.dataset.offline = String(ui.connectionError.value !== null);
|
|
5196
5537
|
});
|
|
5197
|
-
j3(() => {
|
|
5198
|
-
document.body.dataset.sessionActive = String(state.value?.session.active != null);
|
|
5199
|
-
});
|
|
5200
5538
|
j3(() => {
|
|
5201
5539
|
const current = state.value;
|
|
5202
5540
|
const open = current !== null && (detailRun(current, ui) !== null || detailRow(current, ui) !== null);
|
|
@@ -5223,7 +5561,6 @@ j3(() => {
|
|
|
5223
5561
|
});
|
|
5224
5562
|
installKeyboard(handlers);
|
|
5225
5563
|
await refresh();
|
|
5226
|
-
startLocalTick();
|
|
5227
5564
|
var zone = watchZone({
|
|
5228
5565
|
browserZone,
|
|
5229
5566
|
dashboardZone: () => state.value?.timeZone,
|