daily-focus 1.2.0-dev.7 → 1.2.0-dev.8

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
@@ -1013,9 +1013,6 @@ function subscribe(onState, onError) {
1013
1013
  function postTimeZone(timeZone) {
1014
1014
  return post("/api/timezone", { timeZone });
1015
1015
  }
1016
- function postSession(body) {
1017
- return post("/api/session", body);
1018
- }
1019
1016
  function postBoardRefresh() {
1020
1017
  return post("/api/board/refresh");
1021
1018
  }
@@ -1080,9 +1077,7 @@ var VIEWS = ["today", "board", "tickets", "settings"];
1080
1077
  var RUN_PREFIX = "run:";
1081
1078
  var PANEL_PREFIX = "panel:";
1082
1079
  var VIEW_KEY = "daily-focus:view";
1083
- var UNATTENDED_SEEN_KEY = "daily-focus:unattended-seen";
1084
1080
  var AGENT_RUN_SEEN_KEY = "daily-focus:agent-run-seen";
1085
- var FOCUS_MODE_KEY = "daily-focus:focus-mode";
1086
1081
  var DETAIL_KEY = "daily-focus:detail-for";
1087
1082
  var state = y2(null);
1088
1083
  function createUi(initial = {}) {
@@ -1098,15 +1093,12 @@ function createUi(initial = {}) {
1098
1093
  focusField: y2(initial.focusField ?? null),
1099
1094
  openDrawers: y2(initial.openDrawers ?? /* @__PURE__ */ new Set()),
1100
1095
  ticketMode: y2(initial.ticketMode ?? "sync"),
1101
- unattendedSeen: y2(initial.unattendedSeen ?? null),
1102
1096
  agentReportSeen: y2(initial.agentReportSeen ?? null),
1103
1097
  messageOpen: y2(initial.messageOpen ?? /* @__PURE__ */ new Map()),
1104
1098
  view: y2(initial.view ?? "today"),
1105
- focusMode: y2(initial.focusMode ?? false),
1106
1099
  theme: y2(initial.theme ?? "system"),
1107
1100
  toast: y2(initial.toast ?? null),
1108
1101
  connectionError: y2(initial.connectionError ?? null),
1109
- clock: y2(initial.clock ?? Date.now()),
1110
1102
  settingsReturn: y2(initial.settingsReturn ?? "today"),
1111
1103
  settings: y2(initial.settings ?? null),
1112
1104
  settingsDraft: y2(initial.settingsDraft ?? /* @__PURE__ */ new Map()),
@@ -1125,9 +1117,7 @@ function remembered() {
1125
1117
  return {
1126
1118
  // Settings is a page to visit, so a reload returns to the view underneath.
1127
1119
  view: isView(view) && view !== "settings" ? view : "today",
1128
- unattendedSeen: localStorage.getItem(UNATTENDED_SEEN_KEY),
1129
1120
  agentReportSeen: localStorage.getItem(AGENT_RUN_SEEN_KEY),
1130
- focusMode: localStorage.getItem(FOCUS_MODE_KEY) === "1",
1131
1121
  detailFor: sessionStorage.getItem(DETAIL_KEY)
1132
1122
  };
1133
1123
  } catch {
@@ -1295,208 +1285,6 @@ function ThemeToggle({ ui: ui2, handlers: handlers2 }) {
1295
1285
  );
1296
1286
  }
1297
1287
 
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
1288
  // client/actions.ts
1501
1289
  function afterRender(fn) {
1502
1290
  if (typeof requestAnimationFrame === "function") requestAnimationFrame(fn);
@@ -1550,7 +1338,6 @@ function adoptState(next) {
1550
1338
  state.value = next;
1551
1339
  ui.connectionError.value = null;
1552
1340
  });
1553
- startLocalTick();
1554
1341
  checkStore(next.storeId);
1555
1342
  checkAssetVersion(next.assetVersion);
1556
1343
  }
@@ -1742,53 +1529,6 @@ async function askAgent(run, text) {
1742
1529
  showToast(`Could not ask the morning agent: ${err.message}`);
1743
1530
  }
1744
1531
  }
1745
- async function changeSession(body) {
1746
- if (body.action === "start" && "Notification" in window && Notification.permission === "default") {
1747
- void Notification.requestPermission();
1748
- }
1749
- try {
1750
- state.value = await postSession(body);
1751
- startLocalTick();
1752
- } catch (err) {
1753
- showToast(`Could not change the timer: ${err.message}`);
1754
- }
1755
- }
1756
- var tickTimer = null;
1757
- var announcedOverrunFor = null;
1758
- function startLocalTick() {
1759
- if (tickTimer) clearInterval(tickTimer);
1760
- tickTimer = setInterval(tick, 1e3);
1761
- tick();
1762
- }
1763
- function tick() {
1764
- const active = state.value?.session.active;
1765
- if (!active) {
1766
- document.title = "Daily Focus";
1767
- if (tickTimer) clearInterval(tickTimer);
1768
- tickTimer = null;
1769
- return;
1770
- }
1771
- const nowMs = Date.now();
1772
- ui.clock.value = nowMs;
1773
- const remaining = remainingSeconds(active, nowMs);
1774
- const mm = Math.floor(Math.abs(remaining) / 60);
1775
- const ss = String(Math.floor(Math.abs(remaining) % 60)).padStart(2, "0");
1776
- document.title = `${remaining < 0 ? "+" : ""}${mm}:${ss} \xB7 ${active.title}`;
1777
- if (remaining <= 0 && announcedOverrunFor !== active.startedAt) {
1778
- announcedOverrunFor = active.startedAt;
1779
- announceSessionEnd(active);
1780
- }
1781
- }
1782
- function announceSessionEnd(active) {
1783
- const label = active.minutes === 1 ? "1 minute" : `${active.minutes} minutes`;
1784
- showToast(`${label} in \u2014 still on "${active.title}"?`, null);
1785
- if ("Notification" in window && Notification.permission === "granted") {
1786
- new Notification("Still on this?", {
1787
- body: `${label} in on ${active.title}. Keep going, or stop and log it.`,
1788
- tag: "daily-focus-session"
1789
- });
1790
- }
1791
- }
1792
1532
  function showView(view) {
1793
1533
  if (ui.view.value === view) return;
1794
1534
  if (state.value && !availableViews(state.value).includes(view)) return;
@@ -1837,10 +1577,6 @@ function setTicketMode(mode) {
1837
1577
  closeMenus();
1838
1578
  });
1839
1579
  }
1840
- function setFocusMode(on) {
1841
- ui.focusMode.value = on;
1842
- remember(FOCUS_MODE_KEY, on ? "1" : null);
1843
- }
1844
1580
  function restartToast(page) {
1845
1581
  if (page.restart === "manual") showToast("Saved. Restart the dashboard to apply it.");
1846
1582
  else if (page.restart === "waiting" && state.value?.agentRun?.last?.status === "running") {
@@ -2063,12 +1799,6 @@ var handlers = {
2063
1799
  () => document.querySelector('#tickets .item[data-selected="true"]')?.scrollIntoView({ block: "center", behavior: "smooth" })
2064
1800
  );
2065
1801
  },
2066
- startSession: (id) => void changeSession({ action: "start", id }),
2067
- stopSession: () => void changeSession({ action: "stop" }),
2068
- dismissUnattended: (endedAt) => {
2069
- ui.unattendedSeen.value = endedAt;
2070
- remember(UNATTENDED_SEEN_KEY, endedAt);
2071
- },
2072
1802
  // A nudge on Slack is invisible to GitHub, so it's recorded: the board's nudge
2073
1803
  // timer starts over from it, and the agent reads that the user asked.
2074
1804
  nudge: (id) => void applyAction(id, "nudge"),
@@ -2096,7 +1826,6 @@ var handlers = {
2096
1826
  applyTheme(mode);
2097
1827
  ui.theme.value = mode;
2098
1828
  },
2099
- setFocusMode,
2100
1829
  openHelp: () => document.getElementById("help")?.showModal(),
2101
1830
  hideToast,
2102
1831
  openSettings,
@@ -2117,6 +1846,134 @@ var handlers = {
2117
1846
  listCalendars: () => void listCalendars()
2118
1847
  };
2119
1848
 
1849
+ // client/format.ts
1850
+ var DAY_FORMAT = {
1851
+ weekday: "long",
1852
+ day: "numeric",
1853
+ month: "long"
1854
+ };
1855
+ function formatters(timeZone) {
1856
+ return {
1857
+ time: new Intl.DateTimeFormat(void 0, { timeZone, hour: "2-digit", minute: "2-digit" }),
1858
+ day: new Intl.DateTimeFormat(void 0, { timeZone, ...DAY_FORMAT }),
1859
+ shortDay: new Intl.DateTimeFormat(void 0, { timeZone, day: "numeric", month: "short" }),
1860
+ shortDayYear: new Intl.DateTimeFormat(void 0, { timeZone, day: "numeric", month: "short", year: "numeric" }),
1861
+ weekday: new Intl.DateTimeFormat(void 0, { timeZone, weekday: "long" })
1862
+ };
1863
+ }
1864
+ var clockFormats = formatters();
1865
+ var dateFormats = formatters("UTC");
1866
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
1867
+ function formatted(value, kind) {
1868
+ const bare = typeof value === "string" && DATE_ONLY.test(value);
1869
+ const d4 = bare ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : parseDate(value);
1870
+ return d4 && !Number.isNaN(d4.getTime()) ? (bare ? dateFormats : clockFormats)[kind].format(d4) : "";
1871
+ }
1872
+ function parseDate(value) {
1873
+ if (typeof value !== "string" || value === "") return null;
1874
+ const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
1875
+ if (dateOnly) return new Date(+dateOnly[1], +dateOnly[2] - 1, +dateOnly[3]);
1876
+ const d4 = new Date(value);
1877
+ return Number.isNaN(d4.getTime()) ? null : d4;
1878
+ }
1879
+ function formatTime(value) {
1880
+ return formatted(value, "time");
1881
+ }
1882
+ function formatDay(value) {
1883
+ return formatted(value, "day");
1884
+ }
1885
+ function formatShortDay(value) {
1886
+ return formatted(value, "shortDay");
1887
+ }
1888
+ function formatWeekday(value) {
1889
+ return formatted(value, "weekday");
1890
+ }
1891
+ var dayKeyFmt = null;
1892
+ var calendarZone;
1893
+ function setCalendarZone(timeZone) {
1894
+ if (timeZone === calendarZone) return;
1895
+ calendarZone = timeZone;
1896
+ try {
1897
+ clockFormats = formatters(timeZone);
1898
+ dayKeyFmt = timeZone ? new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }) : null;
1899
+ } catch {
1900
+ dayKeyFmt = null;
1901
+ clockFormats = formatters();
1902
+ }
1903
+ }
1904
+ function dayKey(d4) {
1905
+ if (dayKeyFmt) {
1906
+ const parts = dayKeyFmt.formatToParts(d4);
1907
+ const part = (type) => parts.find((p5) => p5.type === type)?.value ?? "";
1908
+ return `${part("year")}-${part("month")}-${part("day")}`;
1909
+ }
1910
+ const m4 = String(d4.getMonth() + 1).padStart(2, "0");
1911
+ const day = String(d4.getDate()).padStart(2, "0");
1912
+ return `${d4.getFullYear()}-${m4}-${day}`;
1913
+ }
1914
+ function splitKey(key) {
1915
+ const [y4, m4, d4] = key.split("-").map(Number);
1916
+ return [y4, m4 - 1, d4];
1917
+ }
1918
+ function keyAt(ms) {
1919
+ return new Date(ms).toISOString().slice(0, 10);
1920
+ }
1921
+ function addDays(key, days) {
1922
+ const [y4, m4, d4] = splitKey(key);
1923
+ return keyAt(Date.UTC(y4, m4, d4 + days));
1924
+ }
1925
+ function addMonths(key, months) {
1926
+ const [y4, m4, d4] = splitKey(key);
1927
+ const lastDay = new Date(Date.UTC(y4, m4 + months + 1, 0)).getUTCDate();
1928
+ return keyAt(Date.UTC(y4, m4 + months, Math.min(d4, lastDay)));
1929
+ }
1930
+ function daysFromToday(value, now = /* @__PURE__ */ new Date()) {
1931
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
1932
+ const [y4, m4, d4] = splitKey(value);
1933
+ const [ty, tm, td] = splitKey(dayKey(now));
1934
+ return Math.round((Date.UTC(y4, m4, d4) - Date.UTC(ty, tm, td)) / 864e5);
1935
+ }
1936
+ const target = parseDate(value);
1937
+ if (!target) return null;
1938
+ const a4 = Date.UTC(...splitKey(dayKey(now)));
1939
+ const b4 = Date.UTC(...splitKey(dayKey(target)));
1940
+ return Math.round((b4 - a4) / 864e5);
1941
+ }
1942
+ function relativeDay(value, now = /* @__PURE__ */ new Date()) {
1943
+ const diff = daysFromToday(value, now);
1944
+ if (diff === null) return "";
1945
+ if (diff === 0) return "today";
1946
+ if (diff === 1) return "tomorrow";
1947
+ if (diff === -1) return "yesterday";
1948
+ if (diff > 1 && diff <= 7) return `in ${diff} days`;
1949
+ if (diff < -1 && diff >= -7) return `${Math.abs(diff)} days ago`;
1950
+ return formatShortDay(value);
1951
+ }
1952
+ function formatDuration(minutes) {
1953
+ const h5 = Math.floor(minutes / 60);
1954
+ const m4 = minutes % 60;
1955
+ if (h5 === 0) return `${m4} min`;
1956
+ if (m4 === 0) return `${h5} h`;
1957
+ return `${h5} h ${m4} min`;
1958
+ }
1959
+ function relativeTime(value, now = /* @__PURE__ */ new Date()) {
1960
+ const d4 = parseDate(value);
1961
+ if (!d4) return "";
1962
+ const seconds = Math.round((now.getTime() - d4.getTime()) / 1e3);
1963
+ if (seconds < 60) return "just now";
1964
+ const minutes = Math.round(seconds / 60);
1965
+ if (minutes < 60) return `${minutes} min ago`;
1966
+ const hours = Math.round(minutes / 60);
1967
+ if (hours < 48) return `${hours} h ago`;
1968
+ const days = Math.round(hours / 24);
1969
+ if (days <= 30) return `${days} days ago`;
1970
+ return dayKey(d4).slice(0, 4) === dayKey(now).slice(0, 4) ? formatShortDay(value) : formatted(value, "shortDayYear");
1971
+ }
1972
+ function firstLine(text) {
1973
+ const line = String(text).split("\n").find((l5) => l5.trim() !== "") ?? "";
1974
+ return line.replace(/^[#>*\-\s]+/, "").replace(/[*_`]/g, "").trim();
1975
+ }
1976
+
2120
1977
  // client/failure.ts
2121
1978
  function failureNotice(failure, { fold, className, icon, lastGood, now, onRetry }, ui2, handlers2) {
2122
1979
  return el(
@@ -2575,9 +2432,6 @@ function renderItem(item, state2, ui2, handlers2) {
2575
2432
  function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
2576
2433
  const now = new Date(state2.now);
2577
2434
  const selected = ui2.selectedId.value === item.id;
2578
- const active = state2.session?.active ?? null;
2579
- const running = active !== null && active.id === item.id;
2580
- const overrun = running && remainingSeconds(active, ui2.clock.value) < 0;
2581
2435
  const repo = githubRepo(item);
2582
2436
  const title = item.url ? el(
2583
2437
  "a",
@@ -2595,8 +2449,6 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
2595
2449
  "data-selected": String(selected),
2596
2450
  "data-priority": String(item.priority !== void 0 && item.status === "open"),
2597
2451
  "data-pending": String(ui2.pending.value.has(item.id)),
2598
- "data-running": String(running),
2599
- "data-overrun": String(overrun),
2600
2452
  "data-detail": String(ui2.detailFor.value === item.id),
2601
2453
  // A click on the card opens the panel on it: one way in, and the whole card is it.
2602
2454
  onClick: (event) => {
@@ -2616,31 +2468,20 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
2616
2468
  "div",
2617
2469
  { class: "item__body" },
2618
2470
  el("p", { class: "item__title" }, title),
2619
- renderMeta(item, state2, ui2, now),
2471
+ renderMeta(item, state2, now),
2620
2472
  item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null
2621
2473
  ),
2622
- renderActions(item, state2, ui2, handlers2),
2474
+ renderActions(item, ui2, handlers2),
2623
2475
  ui2.menuFor.value === item.id ? renderSnoozeMenu(item, now, handlers2) : null
2624
2476
  );
2625
2477
  }
2626
- function renderMeta(item, state2, ui2, now) {
2478
+ function renderMeta(item, state2, now) {
2627
2479
  const pills = [];
2628
2480
  const said = /* @__PURE__ */ new Set();
2629
2481
  const pill = (cls, text) => {
2630
2482
  pills.push(el("span", { class: cls }, text));
2631
2483
  said.add(text.trim().toLowerCase());
2632
2484
  };
2633
- const active = state2.session?.active ?? null;
2634
- if (active && active.id === item.id) {
2635
- pills.push(
2636
- el(
2637
- "span",
2638
- { class: "pill pill--running" },
2639
- el("span", { class: "item__running-clock" }, clock(remainingSeconds(active, ui2.clock.value))),
2640
- " focusing"
2641
- )
2642
- );
2643
- }
2644
2485
  if (item.priority !== void 0 && item.status === "open") {
2645
2486
  pill("pill pill--priority", `#${item.priority}`);
2646
2487
  }
@@ -2692,24 +2533,11 @@ function rowPillTexts(row, state2) {
2692
2533
  function rowPills(row, state2) {
2693
2534
  return rowPillTexts(row, state2).map(([cls, text]) => el("span", { class: cls }, text));
2694
2535
  }
2695
- function renderActions(item, state2, ui2, handlers2) {
2536
+ function renderActions(item, ui2, handlers2) {
2696
2537
  const buttons = [];
2697
2538
  const act = (id, action) => () => handlers2.onAction(id, action);
2698
- const running = state2.session?.active?.id === item.id;
2699
2539
  if (item.status === "open") {
2700
2540
  if (item.kind === "task") {
2701
- buttons.push(
2702
- el(
2703
- "button",
2704
- {
2705
- type: "button",
2706
- class: running ? "button button--primary" : "button",
2707
- title: running ? "Stop the focus session" : "Start a focus session on this",
2708
- onClick: () => running ? handlers2.stopSession() : handlers2.startSession(item.id)
2709
- },
2710
- running ? "Stop" : "Focus"
2711
- )
2712
- );
2713
2541
  buttons.push(el("button", { type: "button", class: "button", onClick: act(item.id, "done") }, "Done"));
2714
2542
  buttons.push(
2715
2543
  el(
@@ -3776,8 +3604,6 @@ var SHORTCUTS = [
3776
3604
  [["Esc"], "Close the menu, then the panel"],
3777
3605
  [["o"], "Open the source link"],
3778
3606
  [["u"], "Undo the last action"],
3779
- [["f"], "Focus mode \u2014 hide all but the top item"],
3780
- [["p"], "Start a focus session on the selected item"],
3781
3607
  [["r"], "Refresh whichever board you are on"],
3782
3608
  [["w"], "Jira tickets: switch between out of sync and working on"],
3783
3609
  [[","], "Settings: your objective, where the agent looks, and the rest"],
@@ -4873,14 +4699,8 @@ function boardRow(name, health, text, action) {
4873
4699
 
4874
4700
  // client/today.ts
4875
4701
  function TodayView({ state: state2, ui: ui2, handlers: handlers2 }) {
4876
- const focusBar = el(
4877
- "p",
4878
- { class: "focus-bar", id: "focus-bar", hidden: !ui2.focusMode.value },
4879
- "Focus mode \u2014 everything but the top item is hidden. ",
4880
- el("button", { type: "button", class: "button", id: "focus-exit", onClick: () => handlers2.setFocusMode(false) }, "Show everything")
4881
- );
4882
- if (state2.setup?.needed) return [focusBar, k(SetupCard, { state: state2, ui: ui2, handlers: handlers2 })];
4883
- return [focusBar, renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2), renderSections(state2, ui2, handlers2)];
4702
+ if (state2.setup?.needed) return [k(SetupCard, { state: state2, ui: ui2, handlers: handlers2 })];
4703
+ return [renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2), renderSections(state2, ui2, handlers2)];
4884
4704
  }
4885
4705
  function renderObjective(state2, handlers2 = null) {
4886
4706
  const focus = state2.focus;
@@ -5003,7 +4823,6 @@ function App({ ui: ui2, handlers: handlers2 }) {
5003
4823
  el("a", { class: "skip-link", href: "#main" }, "Skip to content"),
5004
4824
  k(Header, { state: current, ui: ui2, handlers: handlers2 }),
5005
4825
  k(Tabs, { state: current, ui: ui2, handlers: handlers2 }),
5006
- current ? k(Timer, { state: current, ui: ui2, handlers: handlers2 }) : el("div", { class: "timer", id: "timer", hidden: true }),
5007
4826
  el("div", { id: "banners", class: "banners" }, current ? renderBanners(current, ui2, handlers2) : null),
5008
4827
  el(
5009
4828
  "main",
@@ -5121,11 +4940,6 @@ function installKeyboard(handlers2) {
5121
4940
  event.preventDefault();
5122
4941
  moveSelection(-1);
5123
4942
  return;
5124
- case "f":
5125
- if (ui.view.value !== "today") return;
5126
- event.preventDefault();
5127
- handlers2.setFocusMode(!ui.focusMode.value);
5128
- return;
5129
4943
  case "1":
5130
4944
  event.preventDefault();
5131
4945
  handlers2.setView("today");
@@ -5170,7 +4984,6 @@ function installKeyboard(handlers2) {
5170
4984
  if (!item) return;
5171
4985
  const boardRow2 = ui.view.value !== "today";
5172
4986
  const status = "status" in item ? item.status : null;
5173
- const kind = "kind" in item ? item.kind : null;
5174
4987
  switch (event.key) {
5175
4988
  case "e":
5176
4989
  event.preventDefault();
@@ -5193,12 +5006,6 @@ function installKeyboard(handlers2) {
5193
5006
  event.preventDefault();
5194
5007
  handlers2.openDetail(item.id, "assistant");
5195
5008
  break;
5196
- case "p":
5197
- event.preventDefault();
5198
- if (boardRow2) break;
5199
- if (state.value?.session.active?.id === item.id) handlers2.stopSession();
5200
- else if (status === "open" && kind === "task") handlers2.startSession(item.id);
5201
- break;
5202
5009
  case "o":
5203
5010
  if (item.url) {
5204
5011
  event.preventDefault();
@@ -5218,15 +5025,9 @@ R(k(App, { ui, handlers }), root);
5218
5025
  j3(() => {
5219
5026
  document.body.dataset.view = ui.view.value;
5220
5027
  });
5221
- j3(() => {
5222
- document.body.dataset.focusMode = String(ui.focusMode.value);
5223
- });
5224
5028
  j3(() => {
5225
5029
  document.body.dataset.offline = String(ui.connectionError.value !== null);
5226
5030
  });
5227
- j3(() => {
5228
- document.body.dataset.sessionActive = String(state.value?.session.active != null);
5229
- });
5230
5031
  j3(() => {
5231
5032
  const current = state.value;
5232
5033
  const open = current !== null && (detailRun(current, ui) !== null || detailRow(current, ui) !== null);
@@ -5253,7 +5054,6 @@ j3(() => {
5253
5054
  });
5254
5055
  installKeyboard(handlers);
5255
5056
  await refresh();
5256
- startLocalTick();
5257
5057
  var zone = watchZone({
5258
5058
  browserZone,
5259
5059
  dashboardZone: () => state.value?.timeZone,