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/README.md +0 -9
- package/SETUP.md +3 -5
- package/dist/{audit-7YAJYHZF.js → audit-DNTFZOHH.js} +3 -3
- package/dist/{chunk-4DCBCR4D.js → chunk-6SMUSPZG.js} +2 -2
- package/dist/{chunk-JQGYCWRX.js → chunk-GQ3NCWRT.js} +7 -175
- package/dist/{chunk-RNE27FQY.js → chunk-KZNY45XW.js} +0 -5
- package/dist/{chunk-UY6HQLGE.js → chunk-P3E2B2SD.js} +1 -1
- package/dist/cli.js +6 -6
- package/dist/{demo-5I4BNHW2.js → demo-6Q22AAC7.js} +2 -21
- package/dist/{init-57WHZZNG.js → init-XDMAXU2S.js} +1 -9
- package/dist/{seed-BLFBCHIA.js → seed-TWT7EDXJ.js} +3 -3
- package/dist/{server-UI6WYRTL.js → server-PIYOIUKJ.js} +87 -217
- package/dist/{service-NAAAV67B.js → service-ZUHI3VKH.js} +1 -1
- package/package.json +1 -1
- package/prompts/README.md +2 -3
- package/prompts/morning-brief-personal.md +0 -25
- package/prompts/morning-brief-work.md +0 -28
- package/public/app.js +134 -334
- package/public/style.css +5 -169
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,
|
|
2471
|
+
renderMeta(item, state2, now),
|
|
2620
2472
|
item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null
|
|
2621
2473
|
),
|
|
2622
|
-
renderActions(item,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
4877
|
-
|
|
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,
|