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/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, y4, _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 == y4 && null != v4 && (y4 = 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 = y4, f4;
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, y4, d4, _5, k3, x4, M, I2, P2, A3, H2, T4, j4, F2 = u4.type;
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))), y4 = p5.props, d4 = p5.state, p5.__v = u4, v4) M && null == F2.getDerivedStateFromProps && null != p5.componentWillMount && p5.componentWillMount(), M && null != p5.componentDidMount && p5.__h.push(p5.componentDidMount);
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 !== y4 && null != p5.componentWillReceiveProps && p5.componentWillReceiveProps(x4, P2), u4.__v == t4.__v || !p5.__e && null != p5.shouldComponentUpdate && false === p5.shouldComponentUpdate(x4, p5.__s, P2)) {
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(y4, d4, _5);
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(y4, d4)), 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);
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, y4, w5, _5, m4 = i4.props || d, k3 = t4.props, x4 = t4.type;
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 ((y4 = e4[s4]) && "setAttribute" in y4 == !!x4 && (x4 ? y4.localName == x4 : 3 == y4.nodeType)) {
191
- u4 = y4, e4[s4] = null;
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[(y4 = u4.attributes[s4]).name] = y4.value;
202
- for (s4 in m4) y4 = m4[s4], "dangerouslySetInnerHTML" == s4 ? p5 = y4 : "children" == s4 || s4 in k3 || "value" == s4 && "defaultValue" in k3 || "checked" == s4 && "defaultChecked" in k3 || N(u4, s4, null, y4, o4);
203
- for (s4 in k3) y4 = k3[s4], "children" == s4 ? v4 = y4 : "dangerouslySetInnerHTML" == s4 ? h5 = y4 : "value" == s4 ? w5 = y4 : "checked" == s4 ? _5 = y4 : c4 && "function" != typeof y4 || m4[s4] === y4 || N(u4, s4, y4, m4[s4], o4);
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 d2 = 0;
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
- d2++;
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 y2(i4, t4) {
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 = d2 - 1;
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 === d2) return true;
603
- this.g = d2;
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 d3;
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 (d3) {
769
- var n3 = d3;
770
- d3 = void 0;
807
+ if (d4) {
808
+ var n3 = d4;
809
+ d4 = void 0;
771
810
  n3();
772
811
  }
773
- d3 = i4 && i4.S();
812
+ d4 = i4 && i4.S();
774
813
  }
775
- function y3(i4) {
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
- y3.displayName = "ReactiveTextNode";
806
- Object.defineProperties(l3.prototype, { constructor: { configurable: true, value: void 0 }, type: { configurable: true, value: y3 }, props: { configurable: true, get: function() {
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 = y2(r4);
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 y2(i4, n3);
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 = y2(null);
1139
+ var state = y3(null);
1088
1140
  function createUi(initial = {}) {
1089
1141
  return {
1090
- selectedId: y2(initial.selectedId ?? null),
1091
- clickedId: y2(initial.clickedId ?? null),
1092
- pending: y2(initial.pending ?? /* @__PURE__ */ new Set()),
1093
- menuFor: y2(initial.menuFor ?? null),
1094
- statusFor: y2(initial.statusFor ?? null),
1095
- detailFor: y2(initial.detailFor ?? null),
1096
- noteDraft: y2(initial.noteDraft ?? ""),
1097
- assistantDraft: y2(initial.assistantDraft ?? ""),
1098
- focusField: y2(initial.focusField ?? null),
1099
- openDrawers: y2(initial.openDrawers ?? /* @__PURE__ */ new Set()),
1100
- ticketMode: y2(initial.ticketMode ?? "sync"),
1101
- unattendedSeen: y2(initial.unattendedSeen ?? null),
1102
- agentReportSeen: y2(initial.agentReportSeen ?? null),
1103
- messageOpen: y2(initial.messageOpen ?? /* @__PURE__ */ new Map()),
1104
- view: y2(initial.view ?? "today"),
1105
- focusMode: y2(initial.focusMode ?? false),
1106
- theme: y2(initial.theme ?? "system"),
1107
- toast: y2(initial.toast ?? null),
1108
- connectionError: y2(initial.connectionError ?? null),
1109
- clock: y2(initial.clock ?? Date.now()),
1110
- settingsReturn: y2(initial.settingsReturn ?? "today"),
1111
- settings: y2(initial.settings ?? null),
1112
- settingsDraft: y2(initial.settingsDraft ?? /* @__PURE__ */ new Map()),
1113
- settingsError: y2(initial.settingsError ?? null),
1114
- settingsSaving: y2(initial.settingsSaving ?? false),
1115
- texts: y2(initial.texts ?? {}),
1116
- calendars: y2(initial.calendars ?? null)
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 !== "note") state.value = optimistic(current, id, action, extra);
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 === "note") {
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, fromDraft) {
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
- if (fromDraft) {
1880
- const draft = new Map(ui.settingsDraft.value);
1881
- for (const [key, value] of Object.entries(values)) if (draft.get(key) === value) draft.delete(key);
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
- if (fromDraft) ui.settingsError.value = err.message;
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
- startSession: (id) => void changeSession({ action: "start", id }),
2063
- stopSession: () => void changeSession({ action: "stop" }),
2064
- dismissUnattended: (endedAt) => {
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: (keys) => void sendSettings(Object.fromEntries([...ui.settingsDraft.value].filter(([key]) => !keys || keys.includes(key))), true),
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/failure.ts
2117
- function failureNotice(failure, { fold, className, icon, lastGood, now, onRetry }, ui2, handlers2) {
2118
- return el(
2119
- "details",
2120
- {
2121
- class: `${className} failure`,
2122
- open: ui2.openDrawers.value.has(fold),
2123
- onToggle: (event) => handlers2.toggleDrawer(fold, event.currentTarget.open)
2124
- },
2125
- el(
2126
- "summary",
2127
- { class: "failure__summary", title: "Show what went wrong" },
2128
- icon ? el("span", { class: "banner__icon", "aria-hidden": "true" }, icon) : null,
2129
- // Plain text, never Markdown: it quotes what GitHub, acli and macOS said.
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
- function when(value, now) {
2165
- const days = daysFromToday(value, now);
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
- out.push(el("pre", null, code.join("\n")));
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, ui2, now),
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, state2, ui2, handlers2),
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, ui2, now) {
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
- function renderActions(item, state2, ui2, handlers2) {
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
- if (rows.length === 0) continue;
2909
- parts.push(section(COURT_TITLE[court], rows, state2, ui2, handlers2, null, renderPullRow));
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 Jira has for 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.",
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; it reads and drafts, never sends or posts. 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.`
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
- const from = parseDate(run.startedAt);
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
- enabled ? renderAssistantSection(row, facts, state2, entry, ui2, handlers2, askInput) : entry.turns.length ? renderAssistantHistory(state2, entry) : null
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(row, facts, state2, entry, ui2, handlers2, inputRef) {
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
- entry.turns.length ? el(
4281
+ el(
4217
4282
  "div",
4218
4283
  { class: "assistant__turns" },
4219
- entry.turns.map((turn) => renderAssistantTurn(turn, label))
4220
- ) : null,
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" : "What do you need help with?",
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
- const status = turn.status === "running" ? el("p", { class: "assistant__status" }, "Working\u2026") : 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;
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
- turn.reply ? el("div", { class: "assistant__reply" }, renderMarkdownBlocks(turn.reply)) : null,
4308
- status
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, label = "Save objective") {
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" }, label))
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 choices2 = setting.kind === "flag" ? [{ value: "on", label: "On" }, { value: "off", label: "Off" }] : setting.choices ?? [];
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
- choices2.map((choice) => el("option", { value: choice.value, key: choice.value }, choice.label))
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 listed = ui2.calendars.value;
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 listed === "string" ? el("p", { class: "settings-card__warning" }, listed) : null,
4559
- Array.isArray(listed) ? el(
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
- listed.map(
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
- var CLI_LABEL = { claude: "Claude Code", codex: "Codex" };
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 running = agent.last?.status === "running";
4610
- const placeholders = setup.sources.exists && setup.sources.placeholders > 0;
4611
- return el(
4612
- "section",
4613
- { class: "setup", id: "setup", "aria-labelledby": "setup-title" },
4614
- el("h2", { class: "setup__title", id: "setup-title" }, "Set up your morning brief"),
4615
- el(
4616
- "p",
4617
- { class: "setup__lede" },
4618
- "Each morning an agent reads your mail, calendar, pull requests and the rest, and writes a short brief of what needs to get done today. The steps below get it ready for its first run. Everything here can be changed later in ",
4619
- el("button", { type: "button", class: "link-button", onClick: () => handlers2.openSettings() }, "Settings"),
4620
- "."
4621
- ),
4622
- el(
4623
- "ol",
4624
- { class: "setup__steps" },
4625
- step(
4626
- setup.profileChosen,
4627
- "What should it brief?",
4628
- setup.profileChosen ? el("p", null, setup.profile === "personal" ? "Your personal life." : "Your work.") : [
4629
- el(
4630
- "p",
4631
- null,
4632
- "Work, or home: mail, family calendars, reminders and messages. Run a second dashboard for the other one."
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
- "li",
4836
- { class: "setup__board", "data-health": health, key: name },
4837
- el("span", { class: "setup__board-mark", "aria-label": HEALTH_LABEL[health], title: HEALTH_LABEL[health] }, HEALTH_MARK[health]),
4838
- el("span", { class: "setup__board-name" }, name),
4839
- el("span", { class: "setup__board-text" }, text),
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
- const focusBar = el(
4847
- "p",
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(progress) {
4910
- if (!progress) return null;
4911
- if (progress.workingDaysSince === null) {
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 = progress.workingDaysSince;
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, progress.lastTitle);
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 boardRow2 = ui.view.value !== "today";
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 (!boardRow2 && status === "open") handlers2.onAction(item.id, "done");
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 (!boardRow2 && status === "open") handlers2.onAction(item.id, "dismiss");
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.focusMode = String(ui.focusMode.value);
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,