daily-focus 1.2.0-dev.9 → 2.0.0-dev.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +18 -4
  2. package/SETUP.md +156 -105
  3. package/dist/{audit-DNTFZOHH.js → audit-AQUBVWPA.js} +4 -3
  4. package/dist/{chunk-KZNY45XW.js → chunk-72NUDSOV.js} +126 -119
  5. package/dist/{chunk-GQ3NCWRT.js → chunk-ITUK7XKS.js} +6 -19
  6. package/dist/{chunk-KL6RU4XT.js → chunk-JTMBBUMD.js} +316 -10
  7. package/dist/chunk-QG4XXKBP.js +18 -0
  8. package/dist/chunk-U6EPOXEW.js +570 -0
  9. package/dist/{chunk-P3E2B2SD.js → chunk-WPXIQPYX.js} +1 -1
  10. package/dist/{chunk-6SMUSPZG.js → chunk-YPGKXTBX.js} +2 -2
  11. package/dist/cli.js +19 -12
  12. package/dist/{demo-TIIXODJG.js → demo-KE6WDWMA.js} +159 -37
  13. package/dist/{init-XDMAXU2S.js → init-5KJVHWJU.js} +41 -6
  14. package/dist/{seed-TWT7EDXJ.js → seed-T4VEY6DT.js} +4 -3
  15. package/dist/{server-WT4CQ3VX.js → server-FFRNYEAO.js} +2000 -743
  16. package/dist/{service-ZUHI3VKH.js → service-LQ46YRCZ.js} +7 -6
  17. package/package.json +6 -2
  18. package/prompts/README.md +20 -4
  19. package/prompts/assistant.md +7 -1
  20. package/prompts/morning-brief-personal.md +37 -0
  21. package/prompts/morning-brief-work.md +37 -0
  22. package/prompts/onboarding.md +41 -0
  23. package/public/app.js +806 -287
  24. package/public/fonts/IBMPlexSans-Variable.woff2 +0 -0
  25. package/public/fonts/IBMPlexSerif-Italic.woff2 +0 -0
  26. package/public/fonts/IBMPlexSerif-Medium.woff2 +0 -0
  27. package/public/fonts/IBMPlexSerif-Regular.woff2 +0 -0
  28. package/public/fonts/IBMPlexSerif-SemiBold.woff2 +0 -0
  29. package/public/fonts/OFL.txt +93 -0
  30. package/public/style.css +1686 -481
  31. package/dist/chunk-V2UN3JYL.js +0 -140
package/public/app.js CHANGED
@@ -1110,6 +1110,24 @@ function postObjective(objective, blocker) {
1110
1110
  function postListCalendars() {
1111
1111
  return postJson("/api/calendars/list", {});
1112
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
+ }
1113
1131
 
1114
1132
  // client/state.ts
1115
1133
  var VIEWS = ["today", "board", "tickets", "settings"];
@@ -1144,7 +1162,14 @@ function createUi(initial = {}) {
1144
1162
  settingsError: y3(initial.settingsError ?? null),
1145
1163
  settingsSaving: y3(initial.settingsSaving ?? false),
1146
1164
  texts: y3(initial.texts ?? {}),
1147
- calendars: y3(initial.calendars ?? null)
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)
1148
1173
  };
1149
1174
  }
1150
1175
  function isView(value) {
@@ -1666,7 +1691,7 @@ async function loadSettings() {
1666
1691
  showToast(`Could not load the settings: ${err.message}`);
1667
1692
  }
1668
1693
  }
1669
- async function sendSettings(values, fromDraft) {
1694
+ async function sendSettings(values) {
1670
1695
  if (ui.settingsSaving.value) return;
1671
1696
  n2(() => {
1672
1697
  ui.settingsSaving.value = true;
@@ -1676,17 +1701,14 @@ async function sendSettings(values, fromDraft) {
1676
1701
  const page = await postSettings(values);
1677
1702
  n2(() => {
1678
1703
  ui.settings.value = page;
1679
- if (fromDraft) {
1680
- const draft = new Map(ui.settingsDraft.value);
1681
- for (const [key, value] of Object.entries(values)) if (draft.get(key) === value) draft.delete(key);
1682
- ui.settingsDraft.value = draft;
1683
- }
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;
1684
1707
  });
1685
1708
  restartToast(page);
1686
1709
  if (page.restart === "waiting") void followRestart();
1687
1710
  } catch (err) {
1688
- if (fromDraft) ui.settingsError.value = err.message;
1689
- else showToast(`Could not save: ${err.message}`);
1711
+ ui.settingsError.value = err.message;
1690
1712
  } finally {
1691
1713
  ui.settingsSaving.value = false;
1692
1714
  }
@@ -1764,6 +1786,60 @@ async function listCalendars() {
1764
1786
  ui.calendars.value = err.message;
1765
1787
  }
1766
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
+ }
1767
1843
  function openSettings(section2 = null) {
1768
1844
  if (ui.view.value !== "settings") {
1769
1845
  try {
@@ -1897,14 +1973,39 @@ var handlers = {
1897
1973
  ui.settingsDraft.value = /* @__PURE__ */ new Map();
1898
1974
  ui.settingsError.value = null;
1899
1975
  }),
1900
- saveSettings: (keys) => void sendSettings(Object.fromEntries([...ui.settingsDraft.value].filter(([key]) => !keys || keys.includes(key))), true),
1901
- chooseSettings: (values) => void sendSettings(values, false),
1976
+ saveSettings: () => void sendSettings(Object.fromEntries(ui.settingsDraft.value)),
1902
1977
  loadText: (name) => void loadText(name),
1903
1978
  editText: (name, text) => patchText(name, { draft: text }),
1904
1979
  saveText: (name, overwrite) => void saveText(name, overwrite),
1905
1980
  reloadText: (name) => void loadText(name),
1906
1981
  saveObjective: (objective, blocker) => void saveObjective(objective, blocker),
1907
- 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()
1908
2009
  };
1909
2010
 
1910
2011
  // client/format.ts
@@ -2378,10 +2479,11 @@ function agentRunBanner(run, state2, handlers2) {
2378
2479
  }
2379
2480
  };
2380
2481
  if (run.status === "running") {
2482
+ const helping = (run.steps ?? []).filter((step) => step.subagent && step.state === "running").length;
2381
2483
  return bannerNode(
2382
2484
  "info",
2383
2485
  "i",
2384
- `The morning agent is writing a new brief, started ${relativeTime(run.startedAt, now)}.`,
2486
+ `The morning agent is writing a new brief, started ${relativeTime(run.startedAt, now)}` + (helping ? `, with ${helping} ${helping === 1 ? "subagent" : "subagents"} at work.` : "."),
2385
2487
  [el("button", { type: "button", class: "button banner__action", onClick: () => handlers2.stopAgent() }, "Stop")],
2386
2488
  strip
2387
2489
  );
@@ -2538,7 +2640,11 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
2538
2640
  },
2539
2641
  el(
2540
2642
  "span",
2541
- { class: "item__mark" },
2643
+ // A ranked row's number stands in the margin where the source dot would be:
2644
+ // the stylesheet draws it off `data-rank` and hides the dot, since the
2645
+ // source is named in the meta line anyway and the rank is what this row
2646
+ // is for.
2647
+ { class: "item__mark", "data-rank": item.priority !== void 0 && item.status === "open" ? String(item.priority) : void 0 },
2542
2648
  el("span", {
2543
2649
  class: "item__dot",
2544
2650
  style: `background:${sourceColor(item.source)}`,
@@ -2550,7 +2656,8 @@ function ItemRow({ item, state: state2, ui: ui2, handlers: handlers2 }) {
2550
2656
  { class: "item__body" },
2551
2657
  el("p", { class: "item__title" }, title),
2552
2658
  renderMeta(item, state2, now),
2553
- item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null
2659
+ item.detail ? el("p", { class: "item__detail" }, renderMarkdown(item.detail)) : null,
2660
+ renderAhead(item.id, state2)
2554
2661
  ),
2555
2662
  renderActions(item, ui2, handlers2),
2556
2663
  ui2.menuFor.value === item.id ? renderSnoozeMenu(item, now, handlers2) : null
@@ -2563,9 +2670,6 @@ function renderMeta(item, state2, now) {
2563
2670
  pills.push(el("span", { class: cls }, text));
2564
2671
  said.add(text.trim().toLowerCase());
2565
2672
  };
2566
- if (item.priority !== void 0 && item.status === "open") {
2567
- pill("pill pill--priority", `#${item.priority}`);
2568
- }
2569
2673
  for (const [cls, text] of rowPillTexts(item, state2)) pill(cls, text);
2570
2674
  if (item.advancesObjective) {
2571
2675
  pill("pill pill--objective", "objective");
@@ -2614,6 +2718,38 @@ function rowPillTexts(row, state2) {
2614
2718
  function rowPills(row, state2) {
2615
2719
  return rowPillTexts(row, state2).map(([cls, text]) => el("span", { class: cls }, text));
2616
2720
  }
2721
+ var READING = {
2722
+ "assess-review": "Reading the review\u2026",
2723
+ "explain-ci": "Reading the failing checks\u2026"
2724
+ };
2725
+ function renderAhead(id, state2) {
2726
+ const auto = state2.assistant?.auto;
2727
+ const keys = auto?.rows?.[id];
2728
+ if (!auto || !keys?.length) return null;
2729
+ const turn = (state2.assistant.items?.[id]?.turns ?? []).findLast((candidate) => candidate.auto !== null && keys.includes(candidate.auto));
2730
+ const mark = el("span", { class: "item__ahead-mark", "aria-hidden": "true" }, "\u2726");
2731
+ if (!turn) {
2732
+ 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;
2733
+ }
2734
+ if (turn.status === "running") {
2735
+ return el("p", { class: "item__ahead item__ahead--quiet", "data-status": "running" }, mark, READING[turn.action ?? ""] ?? "Reading\u2026");
2736
+ }
2737
+ if (turn.status === "failed") {
2738
+ return el("p", { class: "item__ahead item__ahead--quiet" }, mark, "Could not answer ahead; the panel says why.");
2739
+ }
2740
+ const verdict = turn.status === "done" ? verdictLine(turn.reply) : "";
2741
+ if (!verdict) return null;
2742
+ return el(
2743
+ "p",
2744
+ { class: "item__ahead", title: `Answered ahead ${relativeTime(turn.endedAt ?? turn.startedAt, new Date(state2.now))}. Open the panel for the whole answer.` },
2745
+ mark,
2746
+ renderMarkdown(verdict)
2747
+ );
2748
+ }
2749
+ function verdictLine(reply) {
2750
+ const line = reply.split("\n").find((candidate) => candidate.trim() !== "") ?? "";
2751
+ return line.replace(/^\s*(?:#{1,6}\s+|>\s*|[-*•]\s+|\d+[.)]\s+)/, "").trim();
2752
+ }
2617
2753
  function renderActions(item, ui2, handlers2) {
2618
2754
  const buttons = [];
2619
2755
  const act = (id, action) => () => handlers2.onAction(id, action);
@@ -2907,7 +3043,8 @@ function PullRow({ item: row, state: state2, ui: ui2, handlers: handlers2 }) {
2907
3043
  ),
2908
3044
  renderPullMeta(row, state2, now),
2909
3045
  renderPullReasons(row, now),
2910
- renderCancelledChecks(row)
3046
+ renderCancelledChecks(row),
3047
+ renderAhead(row.id, state2)
2911
3048
  ),
2912
3049
  renderPullActions(row, ui2, handlers2),
2913
3050
  ui2.menuFor.value === row.id ? renderSnoozeMenu(row, now, handlers2, { indefinite: false }) : null
@@ -3800,6 +3937,7 @@ function describeWhen(value, now) {
3800
3937
  return `${named ? day : date && date.getFullYear() === now.getFullYear() ? formatShortDay(value) : formatDay(value)} at ${formatTime(value)}`;
3801
3938
  }
3802
3939
  function runReport(run, ui2, handlers2) {
3940
+ if (run.steps?.length) return runWork(run, ui2, handlers2);
3803
3941
  const messages = run.messages ?? (run.report ? [run.report] : []);
3804
3942
  const running = run.status === "running";
3805
3943
  const earlier = !running && messages.length && messages.at(-1) === run.report ? messages.slice(0, -1) : messages;
@@ -3858,6 +3996,92 @@ function runReport(run, ui2, handlers2) {
3858
3996
  ) : null
3859
3997
  );
3860
3998
  }
3999
+ function runWork(run, ui2, handlers2) {
4000
+ const running = run.status === "running";
4001
+ if (running) {
4002
+ return el(
4003
+ "section",
4004
+ { class: "flyout__section" },
4005
+ el("h3", { class: "flyout__label" }, "What it is doing"),
4006
+ runSteps(run, run.steps, null, ui2, handlers2)
4007
+ );
4008
+ }
4009
+ const last = run.steps.findLast((step) => step.by === null && step.kind === "message");
4010
+ const steps2 = last && sameWords(last.text, run.report) ? run.steps.filter((step) => step !== last) : run.steps;
4011
+ const handoffs = steps2.filter((step) => step.subagent).length;
4012
+ return el(
4013
+ "section",
4014
+ { class: "flyout__section" },
4015
+ el("h3", { class: "flyout__label" }, run.status === "done" ? "Its report" : "What it said last"),
4016
+ run.report ? el("div", { class: "assistant__reply" }, renderMarkdownBlocks(run.report)) : el("p", { class: "flyout__empty" }, "It said nothing."),
4017
+ steps2.length ? el(
4018
+ "details",
4019
+ { class: "agent-run__list" },
4020
+ el(
4021
+ "summary",
4022
+ null,
4023
+ `How it got there, in ${steps2.length} ${steps2.length === 1 ? "step" : "steps"}` + (handoffs ? `, with ${handoffs} ${handoffs === 1 ? "subagent" : "subagents"}` : "")
4024
+ ),
4025
+ runSteps(run, steps2, null, ui2, handlers2)
4026
+ ) : null
4027
+ );
4028
+ }
4029
+ var NESTING = 4;
4030
+ function runSteps(run, steps2, by, ui2, handlers2, depth = 0) {
4031
+ const own = steps2.filter((step) => step.by === by);
4032
+ return el(
4033
+ "ol",
4034
+ { class: "assistant__steps agent-run__steps" },
4035
+ own.length ? null : el("li", { class: "assistant__step assistant__step--message" }, "Nothing yet."),
4036
+ own.map((step, index) => {
4037
+ if (step.kind === "message") {
4038
+ return el("li", { key: index, class: "agent-run__said" }, renderMarkdownBlocks(step.text));
4039
+ }
4040
+ if (step.subagent && depth < NESTING) return runHandoff(run, steps2, step, ui2, handlers2, depth, index);
4041
+ return el(
4042
+ "li",
4043
+ { key: index, class: "assistant__step", "data-state": step.state },
4044
+ el("span", { class: "assistant__step-text" }, step.text),
4045
+ step.detail ? el("code", { class: "assistant__step-detail", title: step.detail }, step.detail) : null
4046
+ );
4047
+ })
4048
+ );
4049
+ }
4050
+ function runHandoff(run, steps2, step, ui2, handlers2, depth, index) {
4051
+ const id = step.subagent;
4052
+ const own = steps2.filter((candidate) => candidate.by === id);
4053
+ const latest = own.at(-1);
4054
+ const calls = own.filter((candidate) => candidate.kind === "tool").length;
4055
+ const doing = step.state === "running" ? latest ? latest.kind === "message" ? firstLine(latest.text) : latest.text : "starting" : `${calls} ${calls === 1 ? "step" : "steps"}`;
4056
+ const key = `${run.id}:${id}`;
4057
+ const fallback = step.state === "running";
4058
+ const open = ui2.messageOpen.value.has(key) ? ui2.messageOpen.value.get(key) : fallback;
4059
+ return el(
4060
+ "li",
4061
+ { key: index, class: "assistant__step agent-run__handoff", "data-state": step.state },
4062
+ el(
4063
+ "details",
4064
+ {
4065
+ open,
4066
+ // As the message folds do: a toggle back to the default is forgotten.
4067
+ onToggle: (event) => {
4068
+ const isOpen = event.currentTarget.open;
4069
+ handlers2.toggleMessage(key, isOpen === fallback ? null : isOpen);
4070
+ }
4071
+ },
4072
+ el(
4073
+ "summary",
4074
+ null,
4075
+ el("span", { class: "assistant__step-text" }, `Subagent: ${step.detail ?? "unnamed"}`),
4076
+ el("span", { class: "assistant__step-detail" }, doing)
4077
+ ),
4078
+ runSteps(run, steps2, id, ui2, handlers2, depth + 1)
4079
+ )
4080
+ );
4081
+ }
4082
+ function sameWords(a4, b4) {
4083
+ return a4.replace(/\s+/g, " ").trim() === b4.replace(/\s+/g, " ").trim();
4084
+ }
3861
4085
  function runStatus(run, now, handlers2) {
3862
4086
  if (run.status === "running") {
3863
4087
  return el(
@@ -4225,6 +4449,7 @@ function renderAssistantTurn(turn, label, handlers2) {
4225
4449
  const action = turn.action ? label.get(turn.action) : null;
4226
4450
  const extra = action && turn.request.startsWith(action.request) ? turn.request.slice(action.request.length).trim() : null;
4227
4451
  const asked = action ? [el("strong", null, action.label), extra ? ` \u2014 ${extra}` : ""] : [turn.request];
4452
+ 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"));
4228
4453
  const { body, created } = turn.status === "done" ? splitCreated(turn.reply) : { body: turn.reply, created: [] };
4229
4454
  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;
4230
4455
  return el(
@@ -4245,35 +4470,35 @@ function renderAssistantTurn(turn, label, handlers2) {
4245
4470
  );
4246
4471
  }
4247
4472
  function renderWork(turn) {
4248
- const steps = turn.steps ?? [];
4473
+ const steps2 = turn.steps ?? [];
4249
4474
  if (turn.status === "running") {
4250
4475
  return el(
4251
4476
  "div",
4252
4477
  { class: "assistant__work" },
4253
4478
  el("p", { class: "assistant__status assistant__status--working" }, "Working \xB7 ", k(Elapsed, { since: turn.startedAt })),
4254
- steps.length ? renderSteps(steps) : null
4479
+ steps2.length ? renderSteps(steps2) : null
4255
4480
  );
4256
4481
  }
4257
4482
  const took = durationBetween(turn.startedAt, turn.endedAt);
4258
- if (!steps.length) return took ? el("p", { class: "assistant__took" }, `Answered in ${took}`) : null;
4259
- const failed = steps.filter((step2) => step2.state === "failed").length;
4483
+ if (!steps2.length) return took ? el("p", { class: "assistant__took" }, `Answered in ${took}`) : null;
4484
+ const failed = steps2.filter((step) => step.state === "failed").length;
4260
4485
  const summary = [
4261
4486
  took ? `${turn.status === "done" ? "Worked" : "Ran"} for ${took}` : null,
4262
- `${steps.length} ${steps.length === 1 ? "step" : "steps"}`,
4487
+ `${steps2.length} ${steps2.length === 1 ? "step" : "steps"}`,
4263
4488
  failed ? `${failed} failed` : null
4264
4489
  ].filter(Boolean).join(" \xB7 ");
4265
- return el("details", { class: "assistant__work", open: turn.status === "failed" }, el("summary", null, summary), renderSteps(steps));
4490
+ return el("details", { class: "assistant__work", open: turn.status === "failed" }, el("summary", null, summary), renderSteps(steps2));
4266
4491
  }
4267
- function renderSteps(steps) {
4492
+ function renderSteps(steps2) {
4268
4493
  return el(
4269
4494
  "ol",
4270
4495
  { class: "assistant__steps" },
4271
- steps.map(
4272
- (step2, index) => step2.kind === "message" ? el("li", { key: index, class: "assistant__step assistant__step--message" }, renderMarkdown(step2.text.trim())) : el(
4496
+ steps2.map(
4497
+ (step, index) => step.kind === "message" ? el("li", { key: index, class: "assistant__step assistant__step--message" }, renderMarkdown(step.text.trim())) : el(
4273
4498
  "li",
4274
- { key: index, class: "assistant__step", "data-state": step2.state },
4275
- el("span", { class: "assistant__step-text" }, step2.text),
4276
- step2.detail ? el("code", { class: "assistant__step-detail", title: step2.detail }, step2.detail) : null
4499
+ { key: index, class: "assistant__step", "data-state": step.state },
4500
+ el("span", { class: "assistant__step-text" }, step.text),
4501
+ step.detail ? el("code", { class: "assistant__step-detail", title: step.detail }, step.detail) : null
4277
4502
  )
4278
4503
  )
4279
4504
  );
@@ -4299,6 +4524,430 @@ function splitCreated(reply) {
4299
4524
  return { body: created.length ? kept.join("\n").trim() : reply, created };
4300
4525
  }
4301
4526
 
4527
+ // client/onboarding.ts
4528
+ var SECONDS_PER_STEP = 20;
4529
+ function OnboardingView({ state: state2, ui: ui2, handlers: handlers2 }) {
4530
+ const flow = state2.onboarding;
4531
+ const open = flow.steps.filter((step) => step.status === "open");
4532
+ const editing = ui2.setupEditing.value;
4533
+ h2(() => {
4534
+ if (!flow.detected) handlers2.detectSetup();
4535
+ }, [flow.detected]);
4536
+ return el(
4537
+ "div",
4538
+ { class: "onboarding" },
4539
+ el(
4540
+ "header",
4541
+ { class: "onboarding__bar" },
4542
+ el("span", { class: "onboarding__name" }, "Daily Focus"),
4543
+ progress(flow)
4544
+ ),
4545
+ el(
4546
+ "main",
4547
+ { class: "onboarding__column", id: "main" },
4548
+ flow.steps.length > 0 && flow.steps.every((step) => step.status === "open") && flow.steps[0].id === flow.current ? el(
4549
+ "p",
4550
+ { class: "onboarding__lede" },
4551
+ "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."
4552
+ ) : null,
4553
+ el(
4554
+ "ol",
4555
+ { class: "onboarding__thread" },
4556
+ flow.steps.map((step) => {
4557
+ if (step.id === flow.current || step.id === editing) {
4558
+ return el("li", { key: step.id }, k(StepCard, { step, ui: ui2, handlers: handlers2, current: step.id === flow.current }));
4559
+ }
4560
+ if (step.status === "open") return null;
4561
+ return el("li", { key: step.id }, doneRow(step, handlers2, ui2.setupBusy.value !== null));
4562
+ }),
4563
+ flow.current === null ? el("li", { key: "finish" }, k(FinishCard, { flow, ui: ui2, handlers: handlers2 })) : ahead(flow, open)
4564
+ )
4565
+ )
4566
+ );
4567
+ }
4568
+ function progress(flow) {
4569
+ const total = flow.steps.length + 1;
4570
+ const left = flow.steps.filter((step) => step.status === "open").length;
4571
+ const seconds = left * SECONDS_PER_STEP;
4572
+ 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`;
4573
+ const done = total - left - (flow.current === null ? 0 : 1);
4574
+ return el(
4575
+ "div",
4576
+ { class: "onboarding__progress" },
4577
+ el(
4578
+ "div",
4579
+ { class: "onboarding__track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": total, "aria-valuenow": done, "aria-label": "Setup" },
4580
+ el("i", { style: { width: `${Math.round(done / total * 100)}%` } })
4581
+ ),
4582
+ el("span", null, text)
4583
+ );
4584
+ }
4585
+ function doneRow(step, handlers2, busy) {
4586
+ return el(
4587
+ "div",
4588
+ { class: "onboarding__done", "data-status": step.status, "data-step": step.id },
4589
+ el("span", { class: "onboarding__dot", "aria-hidden": "true" }),
4590
+ el("span", { class: "onboarding__summary" }, step.summary ?? `${step.topic[0].toUpperCase()}${step.topic.slice(1)}: decide later.`),
4591
+ el("button", { type: "button", class: "onboarding__change", disabled: busy, onClick: () => handlers2.editSetupStep(step.id) }, "Change")
4592
+ );
4593
+ }
4594
+ function ahead(flow, open) {
4595
+ const rest = open.filter((step) => step.id !== flow.current);
4596
+ const looking = rest.find((step) => step.proposal?.status === "working");
4597
+ const topics = rest.filter((step) => step !== looking).map((step) => step.topic);
4598
+ const later = topics.length > 4 ? [...topics.slice(0, 3), `${topics.length - 3} more`] : topics;
4599
+ return el(
4600
+ "li",
4601
+ { key: "ahead", class: "onboarding__ahead" },
4602
+ looking ? el(
4603
+ "div",
4604
+ { class: "onboarding__next" },
4605
+ el("span", { class: "onboarding__dot onboarding__dot--looking", "aria-hidden": "true" }),
4606
+ el("p", null, `Next, ${looking.topic}. It's already looking:`),
4607
+ steps(looking)
4608
+ ) : null,
4609
+ el(
4610
+ "p",
4611
+ { class: "onboarding__then" },
4612
+ el("span", { class: "onboarding__dot onboarding__dot--later", "aria-hidden": "true" }),
4613
+ later.length ? `Then ${listed([...later, "your first brief"])}.` : "Then your first brief."
4614
+ )
4615
+ );
4616
+ }
4617
+ function steps(step, count = 3) {
4618
+ const shown = (step.proposal?.steps ?? []).filter((s4) => s4.kind === "tool").slice(-count);
4619
+ const waiting = step.proposal?.status === "working" && !shown.some((s4) => s4.state === "running");
4620
+ return el(
4621
+ "ul",
4622
+ { class: "onboarding__steps" },
4623
+ shown.map((s4, index) => el("li", { key: index, "data-state": s4.state, title: s4.detail ?? void 0 }, s4.text)),
4624
+ waiting ? el("li", { key: "waiting", "data-state": "running" }, shown.length ? "Writing its answer\u2026" : "Starting\u2026") : null
4625
+ );
4626
+ }
4627
+ function listed(names) {
4628
+ if (names.length <= 1) return names.join("");
4629
+ return `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
4630
+ }
4631
+ function baseAnswer(step) {
4632
+ if (step.answer && step.answer.later !== true) return step.answer;
4633
+ return {
4634
+ choices: Object.fromEntries(step.card.choices.map((choice) => [choice.id, choice.selected])),
4635
+ fields: Object.fromEntries(step.card.fields.map((field) => [field.id, field.value]))
4636
+ };
4637
+ }
4638
+ function StepCard({ step, ui: ui2, handlers: handlers2, current }) {
4639
+ const saved = ui2.setupDrafts.value.get(step.id);
4640
+ const draft = saved?.answer ?? baseAnswer(step);
4641
+ const stale = saved !== void 0 && saved.version !== step.card.version;
4642
+ const busy = ui2.setupBusy.value !== null;
4643
+ const error = ui2.setupError.value?.step === step.id ? ui2.setupError.value.message : null;
4644
+ const editing = ui2.setupEditing.value === step.id;
4645
+ const proposal = step.proposal;
4646
+ const looking = proposal?.status === "working";
4647
+ const edit = (next) => handlers2.editSetupDraft(step.id, next, saved?.version ?? step.card.version);
4648
+ const tell = ui2.setupTell.value.get(step.id) ?? "";
4649
+ const empty = step.card.choices.every((choice) => choice.options.length === 0) && step.card.fields.length === 0;
4650
+ const conversation = proposal?.conversation ?? [];
4651
+ const revising = conversation.at(-1)?.from === "you";
4652
+ const confirming = looking && ui2.setupConfirm.value === step.id;
4653
+ 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;
4654
+ return el(
4655
+ "section",
4656
+ { class: "onboarding__card", "data-current": String(current), "data-step": step.id, "aria-labelledby": `setup-${step.id}` },
4657
+ el("span", { class: "onboarding__dot onboarding__dot--open", "aria-hidden": "true" }),
4658
+ el("h2", { id: `setup-${step.id}`, tabIndex: -1 }, step.title),
4659
+ conversation.length ? el(
4660
+ "ol",
4661
+ { class: "onboarding__talk" },
4662
+ conversation.map(
4663
+ (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)
4664
+ )
4665
+ ) : step.card.why && !(looking && empty) ? el("p", { class: "onboarding__why" }, sparkle(), el("span", null, step.card.why)) : null,
4666
+ looking ? el(
4667
+ "div",
4668
+ { class: "onboarding__looking", role: "status" },
4669
+ revising ? null : el("p", null, empty ? `Looking into ${step.topic}\u2026` : "Still looking. You can answer now, or wait for its pick."),
4670
+ steps(step)
4671
+ ) : null,
4672
+ // Under what it said, so a reply reads as one, and what comes back lands here too.
4673
+ step.card.revisable && !looking ? el(
4674
+ "form",
4675
+ {
4676
+ class: "onboarding__tell",
4677
+ onSubmit: (event) => {
4678
+ event.preventDefault();
4679
+ if (tell.trim() && !busy) handlers2.reviseSetup(step.id, tell, draft);
4680
+ }
4681
+ },
4682
+ el("input", {
4683
+ type: "text",
4684
+ value: tell,
4685
+ "aria-label": step.card.reply ?? "Tell it what to change",
4686
+ placeholder: step.card.reply ?? "Tell it what to change",
4687
+ onInput: (event) => handlers2.editSetupTell(step.id, event.currentTarget.value)
4688
+ }),
4689
+ el("button", { type: "submit", class: "button", disabled: busy || tell.trim() === "" }, "Send")
4690
+ ) : null,
4691
+ proposal?.status === "failed" ? el(
4692
+ "p",
4693
+ { class: "onboarding__note" },
4694
+ `It couldn't look into this: ${proposal.error}. `,
4695
+ el("button", { type: "button", class: "link-button", disabled: busy, onClick: () => handlers2.retrySetup(step.id) }, "Try again")
4696
+ ) : null,
4697
+ stale ? el(
4698
+ "p",
4699
+ { class: "onboarding__note" },
4700
+ "It has a new suggestion since you started changing this. ",
4701
+ el("button", { type: "button", class: "link-button", onClick: () => handlers2.editSetupDraft(step.id, null, 0) }, "Use it")
4702
+ ) : null,
4703
+ step.card.problem?.blocking ? problem(step, busy, handlers2) : null,
4704
+ el(
4705
+ "form",
4706
+ {
4707
+ class: "onboarding__form",
4708
+ onSubmit: (event) => {
4709
+ event.preventDefault();
4710
+ if (busy) return;
4711
+ if (looking && !confirming) return handlers2.confirmSetup(step.id);
4712
+ handlers2.answerSetup(step.id, draft);
4713
+ }
4714
+ },
4715
+ // Fields first: a card with both asks for the few words before the list.
4716
+ step.card.fields.length ? el(
4717
+ "div",
4718
+ { class: "onboarding__fields" },
4719
+ step.card.fields.map(
4720
+ (field) => fieldView(field, draft.fields[field.id] ?? "", (value) => edit({ ...draft, fields: { ...draft.fields, [field.id]: value } }))
4721
+ )
4722
+ ) : null,
4723
+ step.card.choices.map(
4724
+ (choice) => k(ChoiceView, {
4725
+ choice,
4726
+ picked: draft.choices[choice.id] ?? [],
4727
+ onPick: (values) => edit({ ...draft, choices: { ...draft.choices, [choice.id]: values } })
4728
+ })
4729
+ ),
4730
+ step.card.problem && !step.card.problem.blocking ? problem(step, busy, handlers2) : null,
4731
+ error ? el("p", { class: "onboarding__error", role: "alert" }, error) : null,
4732
+ confirming ? el(
4733
+ "div",
4734
+ { class: "onboarding__actions onboarding__confirm", role: "alert" },
4735
+ 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?"),
4736
+ el("button", { type: "submit", class: "button button--primary", disabled: busy }, "Continue without it"),
4737
+ el("button", { type: "button", class: "button onboarding__quiet", onClick: () => handlers2.confirmSetup(null) }, "Wait for it")
4738
+ ) : el(
4739
+ "div",
4740
+ { class: "onboarding__actions" },
4741
+ el(
4742
+ "button",
4743
+ { type: "submit", class: "button button--primary", disabled: held !== null, title: held ?? void 0 },
4744
+ ui2.setupBusy.value === step.id ? "Saving\u2026" : step.card.empty && step.card.fields.every((field) => !(draft.fields[field.id] ?? "").trim()) ? step.card.empty : "Continue"
4745
+ ),
4746
+ step.card.later ? el(
4747
+ "button",
4748
+ { type: "button", class: "button onboarding__quiet", disabled: busy, onClick: () => handlers2.answerSetup(step.id, { later: true }) },
4749
+ step.card.later
4750
+ ) : null,
4751
+ editing ? el("button", { type: "button", class: "button onboarding__quiet", onClick: () => handlers2.editSetupStep(null) }, "Cancel") : null
4752
+ )
4753
+ )
4754
+ );
4755
+ }
4756
+ function problem(step, busy, handlers2) {
4757
+ const found = step.card.problem;
4758
+ return el(
4759
+ "div",
4760
+ { class: "onboarding__problem", "data-blocking": String(found.blocking) },
4761
+ el("p", null, found.message),
4762
+ found.links?.length ? el(
4763
+ "p",
4764
+ { class: "onboarding__links" },
4765
+ found.links.map((link) => el("a", { key: link.url, href: link.url, target: "_blank", rel: "noopener" }, link.label))
4766
+ ) : null,
4767
+ found.command ? el("pre", null, el("code", null, found.command)) : null,
4768
+ el("button", { type: "button", class: "button", disabled: busy, onClick: () => handlers2.retrySetup(step.id) }, "Check again")
4769
+ );
4770
+ }
4771
+ function sparkle() {
4772
+ return el(
4773
+ "svg",
4774
+ { class: "onboarding__sparkle", width: 14, height: 14, viewBox: "0 0 14 14", "aria-hidden": "true" },
4775
+ el("path", { d: "M7 0l1.6 5.4L14 7l-5.4 1.6L7 14l-1.6-5.4L0 7l5.4-1.6z", fill: "currentColor" })
4776
+ );
4777
+ }
4778
+ function ChoiceView({ choice, picked, onPick }) {
4779
+ const [adding, setAdding] = d2(false);
4780
+ const [typed, setTyped] = d2("");
4781
+ const known = new Set(choice.options.map((option) => option.value));
4782
+ const own = picked.filter((value) => !known.has(value));
4783
+ const toggle = (value) => {
4784
+ if (!choice.multiple) return onPick([value]);
4785
+ onPick(picked.includes(value) ? picked.filter((v4) => v4 !== value) : [...picked, value]);
4786
+ };
4787
+ const add = () => {
4788
+ const value = typed.trim();
4789
+ if (value) onPick(choice.multiple ? [...picked.filter((v4) => v4 !== value), value] : [value]);
4790
+ setTyped("");
4791
+ setAdding(false);
4792
+ };
4793
+ const adder = choice.custom ? adding ? el(
4794
+ "div",
4795
+ { class: "onboarding__add" },
4796
+ el("input", {
4797
+ type: "text",
4798
+ value: typed,
4799
+ placeholder: choice.custom,
4800
+ "aria-label": choice.custom,
4801
+ ref: (input) => input?.focus(),
4802
+ onInput: (event) => setTyped(event.currentTarget.value),
4803
+ onKeyDown: (event) => {
4804
+ if (event.key === "Enter") {
4805
+ event.preventDefault();
4806
+ add();
4807
+ }
4808
+ if (event.key === "Escape") setAdding(false);
4809
+ }
4810
+ }),
4811
+ el("button", { type: "button", class: "button", onClick: add }, "Add")
4812
+ ) : el("button", { type: "button", class: "onboarding__add-button", onClick: () => setAdding(true) }, choice.multiple ? choice.custom : "Other\u2026") : null;
4813
+ const label = choice.label ? el("legend", { class: "onboarding__legend" }, choice.label) : null;
4814
+ const rows = choice.multiple || choice.options.some((option) => option.detail);
4815
+ if (!rows) {
4816
+ return el(
4817
+ "fieldset",
4818
+ { class: "onboarding__choice" },
4819
+ label,
4820
+ el(
4821
+ "div",
4822
+ { class: "onboarding__pills", role: "radiogroup" },
4823
+ [...choice.options, ...own.map((value) => ({ value, label: value, detail: null, group: null }))].map(
4824
+ (option) => el(
4825
+ "button",
4826
+ {
4827
+ type: "button",
4828
+ key: option.value,
4829
+ role: "radio",
4830
+ class: "onboarding__pill",
4831
+ "aria-checked": String(picked.includes(option.value)),
4832
+ onClick: () => toggle(option.value)
4833
+ },
4834
+ option.label
4835
+ )
4836
+ ),
4837
+ adder
4838
+ )
4839
+ );
4840
+ }
4841
+ const groups = /* @__PURE__ */ new Map();
4842
+ for (const option of choice.options) groups.set(option.group, [...groups.get(option.group) ?? [], option]);
4843
+ if (own.length) groups.set("Added by you", own.map((value) => ({ value, label: value, detail: null, group: "Added by you" })));
4844
+ return el(
4845
+ "fieldset",
4846
+ { class: "onboarding__choice" },
4847
+ label,
4848
+ el(
4849
+ "div",
4850
+ { class: "onboarding__list" },
4851
+ [...groups].map(
4852
+ ([group, options]) => el(
4853
+ "div",
4854
+ { class: "onboarding__group", key: group ?? "" },
4855
+ group ? el("p", { class: "onboarding__group-name" }, group) : null,
4856
+ options.map(
4857
+ (option) => el(
4858
+ "label",
4859
+ { class: "onboarding__option", key: option.value },
4860
+ el("input", {
4861
+ type: choice.multiple ? "checkbox" : "radio",
4862
+ name: choice.id,
4863
+ checked: picked.includes(option.value),
4864
+ onChange: () => toggle(option.value)
4865
+ }),
4866
+ el("span", { class: "onboarding__box", "data-kind": choice.multiple ? "check" : "radio", "aria-hidden": "true" }),
4867
+ el("span", { class: "onboarding__option-text" }, option.label, option.detail ? el("small", null, option.detail) : null)
4868
+ )
4869
+ )
4870
+ )
4871
+ ),
4872
+ choice.options.length === 0 && own.length === 0 && !choice.custom ? el("p", { class: "onboarding__none" }, "Nothing to choose from.") : null,
4873
+ adder ? el("div", { class: "onboarding__adder" }, adder) : null
4874
+ )
4875
+ );
4876
+ }
4877
+ function fieldView(field, value, onInput) {
4878
+ return el(
4879
+ "div",
4880
+ { class: "onboarding__field", key: field.id },
4881
+ el(
4882
+ "label",
4883
+ null,
4884
+ el("span", { class: "onboarding__field-label" }, field.label),
4885
+ el("input", {
4886
+ type: "text",
4887
+ value,
4888
+ placeholder: field.placeholder ?? void 0,
4889
+ onInput: (event) => onInput(event.currentTarget.value)
4890
+ })
4891
+ ),
4892
+ field.help ? el("small", null, field.help) : null,
4893
+ // Offered, never filled in: a click puts one in the field, to edit or keep.
4894
+ field.suggestions?.length ? el(
4895
+ "ul",
4896
+ { class: "onboarding__suggestions" },
4897
+ field.suggestions.map(
4898
+ (suggestion) => el(
4899
+ "li",
4900
+ { key: suggestion.text },
4901
+ el(
4902
+ "button",
4903
+ { type: "button", "aria-pressed": String(value === suggestion.text), onClick: () => onInput(suggestion.text) },
4904
+ el("span", null, suggestion.text),
4905
+ suggestion.detail ? el("small", null, suggestion.detail) : null
4906
+ )
4907
+ )
4908
+ )
4909
+ ) : null
4910
+ );
4911
+ }
4912
+ function FinishCard({ flow, ui: ui2, handlers: handlers2 }) {
4913
+ const writes = flow.steps.find((step) => step.id === "cli")?.status === "answered";
4914
+ const busy = ui2.setupBusy.value !== null;
4915
+ const error = (ui2.setupError.value?.step === "finish" ? ui2.setupError.value.message : null) ?? flow.error;
4916
+ const sources = ui2.setupSources.value;
4917
+ return el(
4918
+ "section",
4919
+ { class: "onboarding__card onboarding__finish", "data-current": "true", "aria-labelledby": "setup-finish" },
4920
+ el("span", { class: "onboarding__dot onboarding__dot--open", "aria-hidden": "true" }),
4921
+ el("h2", { id: "setup-finish", tabIndex: -1 }, writes ? "Ready for your first brief" : "Ready to finish"),
4922
+ el(
4923
+ "p",
4924
+ { class: "onboarding__why" },
4925
+ 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."
4926
+ ),
4927
+ el(
4928
+ "details",
4929
+ {
4930
+ class: "onboarding__preview",
4931
+ onToggle: (event) => {
4932
+ if (event.currentTarget.open && sources === null) handlers2.loadSetupSources();
4933
+ }
4934
+ },
4935
+ el("summary", null, "What the morning agent reads each morning"),
4936
+ 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.")
4937
+ ),
4938
+ error ? el("p", { class: "onboarding__error", role: "alert" }, error) : null,
4939
+ el(
4940
+ "div",
4941
+ { class: "onboarding__actions" },
4942
+ el(
4943
+ "button",
4944
+ { type: "button", class: "button button--primary", disabled: busy, onClick: () => handlers2.finishSetup() },
4945
+ ui2.setupBusy.value === "finish" ? "Saving\u2026" : writes ? "Write my first brief" : "Finish setup"
4946
+ )
4947
+ )
4948
+ );
4949
+ }
4950
+
4302
4951
  // client/settings.ts
4303
4952
  var VIEW_NAME = {
4304
4953
  today: "Today",
@@ -4313,6 +4962,11 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
4313
4962
  }, []);
4314
4963
  const page = ui2.settings.value;
4315
4964
  const draft = ui2.settingsDraft.value;
4965
+ const sections = [
4966
+ { id: "objective", title: "Your objective" },
4967
+ { id: "sources", title: "Where the agent looks" },
4968
+ ...page?.groups ?? []
4969
+ ];
4316
4970
  return el(
4317
4971
  "div",
4318
4972
  { class: "settings" },
@@ -4322,6 +4976,7 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
4322
4976
  el("h2", { class: "settings__title" }, "Settings"),
4323
4977
  el("button", { type: "button", class: "button", onClick: () => handlers2.closeSettings() }, `Back to ${VIEW_NAME[ui2.settingsReturn.value]}`)
4324
4978
  ),
4979
+ k(SectionIndex, { sections }),
4325
4980
  page?.error ? el("p", { class: "settings__problem", role: "alert" }, page.error) : null,
4326
4981
  objectiveCard(state2, ui2, handlers2),
4327
4982
  sourcesCard(state2, ui2, handlers2),
@@ -4346,6 +5001,29 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
4346
5001
  saveBar(ui2, handlers2)
4347
5002
  );
4348
5003
  }
5004
+ function SectionIndex({ sections }) {
5005
+ const [current, setCurrent] = d2(sections[0]?.id ?? null);
5006
+ h2(() => {
5007
+ const look = () => {
5008
+ let seen = sections[0]?.id ?? null;
5009
+ for (const section2 of sections) {
5010
+ const card = document.getElementById(`settings-${section2.id}`);
5011
+ if (card && card.getBoundingClientRect().top <= 120) seen = section2.id;
5012
+ }
5013
+ setCurrent(seen);
5014
+ };
5015
+ look();
5016
+ window.addEventListener("scroll", look, { passive: true });
5017
+ return () => window.removeEventListener("scroll", look);
5018
+ }, [sections.map((section2) => section2.id).join(" ")]);
5019
+ return el(
5020
+ "nav",
5021
+ { class: "settings__nav", "aria-label": "Settings sections" },
5022
+ sections.map(
5023
+ (section2) => el("a", { key: section2.id, href: `#settings-${section2.id}`, "aria-current": section2.id === current ? "true" : void 0 }, section2.title)
5024
+ )
5025
+ );
5026
+ }
4349
5027
  function objectiveCard(state2, ui2, handlers2) {
4350
5028
  const focus = state2.focus;
4351
5029
  return el(
@@ -4373,7 +5051,7 @@ function objectiveCard(state2, ui2, handlers2) {
4373
5051
  )
4374
5052
  );
4375
5053
  }
4376
- function objectiveForm(objective, blocker, handlers2, label = "Save objective") {
5054
+ function objectiveForm(objective, blocker, handlers2) {
4377
5055
  return el(
4378
5056
  "form",
4379
5057
  {
@@ -4404,7 +5082,7 @@ ${blocker}`,
4404
5082
  el("span", { class: "field__label" }, "Blocked on (optional)"),
4405
5083
  el("input", { class: "field__input", name: "blocker", defaultValue: blocker, placeholder: "What is in the way today, if anything", autocomplete: "off" })
4406
5084
  ),
4407
- el("div", { class: "objective-form__actions" }, el("button", { type: "submit", class: "button button--primary" }, label))
5085
+ el("div", { class: "objective-form__actions" }, el("button", { type: "submit", class: "button button--primary" }, "Save objective"))
4408
5086
  );
4409
5087
  }
4410
5088
  function sourcesCard(state2, ui2, handlers2) {
@@ -4474,13 +5152,14 @@ function shownValue(setting, draft) {
4474
5152
  if (draft.has(setting.key)) return draft.get(setting.key) ?? "";
4475
5153
  return setting.source === "default" ? "" : setting.value ?? "";
4476
5154
  }
5155
+ var SEGMENTS = 4;
4477
5156
  function flagValue(raw) {
4478
5157
  if (raw === "") return "";
4479
5158
  return ["off", "false", "0", "no"].includes(raw.trim().toLowerCase()) ? "off" : "on";
4480
5159
  }
4481
5160
  function settingField(setting, draft, handlers2) {
4482
5161
  const id = `setting-${setting.key}`;
4483
- const locked = setting.source === "environment";
5162
+ const locked = setting.source === "fixed";
4484
5163
  const value = shownValue(setting, draft);
4485
5164
  const onChange = (event) => {
4486
5165
  const next = event.currentTarget.value;
@@ -4488,13 +5167,36 @@ function settingField(setting, draft, handlers2) {
4488
5167
  };
4489
5168
  let input;
4490
5169
  if (setting.kind === "choice" || setting.kind === "flag") {
4491
- const choices2 = setting.kind === "flag" ? [{ value: "on", label: "On" }, { value: "off", label: "Off" }] : setting.choices ?? [];
5170
+ const choices = setting.kind === "flag" ? [{ value: "on", label: "On" }, { value: "off", label: "Off" }] : setting.choices ?? [];
4492
5171
  const current = setting.kind === "flag" ? flagValue(value) : value;
4493
- input = el(
5172
+ const fallback = choices.find((choice) => choice.label.toLowerCase() === setting.fallback.toLowerCase());
5173
+ input = choices.length <= SEGMENTS && fallback ? el(
5174
+ "div",
5175
+ { class: "segmented", id, role: "radiogroup", "aria-label": setting.label, "aria-disabled": String(locked) },
5176
+ choices.map((choice) => {
5177
+ const isDefault = choice.value === fallback.value;
5178
+ const checked = current === "" ? isDefault : current === choice.value;
5179
+ return el(
5180
+ "button",
5181
+ {
5182
+ type: "button",
5183
+ key: choice.value,
5184
+ role: "radio",
5185
+ class: "segmented__option",
5186
+ "aria-checked": String(checked),
5187
+ disabled: locked,
5188
+ title: isDefault ? "The default" : void 0,
5189
+ onClick: () => handlers2.editSetting(setting.key, isDefault ? null : choice.value)
5190
+ },
5191
+ choice.label,
5192
+ checked && current === "" ? el("small", null, "default") : null
5193
+ );
5194
+ })
5195
+ ) : el(
4494
5196
  "select",
4495
5197
  { class: "field__input", id, disabled: locked, value: current, onChange },
4496
5198
  el("option", { value: "" }, `Default: ${setting.fallback}`),
4497
- choices2.map((choice) => el("option", { value: choice.value, key: choice.value }, choice.label))
5199
+ choices.map((choice) => el("option", { value: choice.value, key: choice.value }, choice.label))
4498
5200
  );
4499
5201
  } else {
4500
5202
  input = el("input", {
@@ -4510,7 +5212,7 @@ function settingField(setting, draft, handlers2) {
4510
5212
  onInput: onChange
4511
5213
  });
4512
5214
  }
4513
- const note = setting.source === "environment" ? "Set in the environment this dashboard was started with, which wins over this page." : setting.source === "dotenv" && !draft.has(setting.key) ? "From the .env file. Saving a value here takes its place." : null;
5215
+ const note = locked ? "Fixed in the demo, so that nothing in it runs." : null;
4514
5216
  return el(
4515
5217
  "div",
4516
5218
  { class: "field", key: setting.key, "data-changed": String(draft.has(setting.key)) },
@@ -4533,9 +5235,9 @@ function calendarPicker(state2, ui2, handlers2, settings) {
4533
5235
  );
4534
5236
  }
4535
5237
  const setting = settings.find((entry) => entry.key === "DAILY_FOCUS_CALENDARS");
4536
- if (!setting || setting.source === "environment") return null;
5238
+ if (!setting || setting.source === "fixed") return null;
4537
5239
  const chosen = shownValue(setting, ui2.settingsDraft.value).split(",").map((name) => name.trim()).filter(Boolean);
4538
- const listed = ui2.calendars.value;
5240
+ const listed2 = ui2.calendars.value;
4539
5241
  const toggle = (title, on) => {
4540
5242
  const others = chosen.filter((name) => name.toLowerCase() !== title.toLowerCase());
4541
5243
  const next = on ? [...others, title] : others;
@@ -4545,11 +5247,11 @@ function calendarPicker(state2, ui2, handlers2, settings) {
4545
5247
  "div",
4546
5248
  { class: "calendar-picker" },
4547
5249
  el("button", { type: "button", class: "button", onClick: () => handlers2.listCalendars() }, "List the calendars on this Mac"),
4548
- typeof listed === "string" ? el("p", { class: "settings-card__warning" }, listed) : null,
4549
- Array.isArray(listed) ? el(
5250
+ typeof listed2 === "string" ? el("p", { class: "settings-card__warning" }, listed2) : null,
5251
+ Array.isArray(listed2) ? el(
4550
5252
  "ul",
4551
5253
  { class: "calendar-picker__list" },
4552
- listed.map(
5254
+ listed2.map(
4553
5255
  (entry) => el(
4554
5256
  "li",
4555
5257
  { key: `${entry.title}
@@ -4592,249 +5294,61 @@ function saveBar(ui2, handlers2) {
4592
5294
  }
4593
5295
 
4594
5296
  // client/setup.ts
4595
- var CLI_LABEL = { claude: "Claude Code", codex: "Codex" };
4596
- function SetupCard({ state: state2, ui: ui2, handlers: handlers2 }) {
4597
- const setup = state2.setup;
5297
+ function FirstBriefCard({ state: state2, handlers: handlers2 }) {
4598
5298
  const agent = state2.agentRun;
4599
- const running = agent.last?.status === "running";
4600
- const placeholders = setup.sources.exists && setup.sources.placeholders > 0;
4601
- return el(
4602
- "section",
4603
- { class: "setup", id: "setup", "aria-labelledby": "setup-title" },
4604
- el("h2", { class: "setup__title", id: "setup-title" }, "Set up your morning brief"),
4605
- el(
4606
- "p",
4607
- { class: "setup__lede" },
4608
- "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 ",
4609
- el("button", { type: "button", class: "link-button", onClick: () => handlers2.openSettings() }, "Settings"),
4610
- "."
4611
- ),
4612
- el(
4613
- "ol",
4614
- { class: "setup__steps" },
4615
- step(
4616
- setup.profileChosen,
4617
- "What should it brief?",
4618
- setup.profileChosen ? el("p", null, setup.profile === "personal" ? "Your personal life." : "Your work.") : [
4619
- el(
4620
- "p",
4621
- null,
4622
- "Work, or home: mail, family calendars, reminders and messages. Run a second dashboard for the other one."
4623
- ),
4624
- // A choice, so neither answer is drawn as the one to pick.
4625
- choices([
4626
- ["Work", () => handlers2.chooseSettings({ DAILY_FOCUS_PROFILE: "work" })],
4627
- ["Personal life", () => handlers2.chooseSettings({ DAILY_FOCUS_PROFILE: "personal" })]
4628
- ])
4629
- ]
4630
- ),
4631
- step(
4632
- Boolean(state2.focus?.objective),
4633
- "What are you trying to achieve?",
4634
- state2.focus?.objective ? el("p", null, state2.focus.objective) : [
4635
- el("p", null, "The agent ranks the day against it. Leave it for now if nothing stands out."),
4636
- objectiveForm("", "", handlers2, "Set objective")
4637
- ]
4638
- ),
4639
- step(
4640
- agent.cli !== null,
4641
- "Which agent writes the brief?",
4642
- agent.cli ? [
4643
- el(
4644
- "p",
4645
- null,
4646
- `${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."
4647
- ),
4648
- agentAdvanced(agent.cli, ui2, handlers2)
4649
- ] : agentChoice(state2, handlers2)
4650
- ),
4651
- step(setup.sources.exists && setup.sources.placeholders === 0, "Who are you, and where should it look?", sourcesStep(state2, ui2, handlers2)),
4652
- step(null, "What else should the dashboard show?", boards(state2, handlers2)),
4653
- step(
4654
- null,
4655
- "Your first brief",
4656
- [
4657
- el(
4658
- "p",
4659
- null,
4660
- 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.")
4661
- ),
4662
- placeholders ? el("p", { class: "setup__note setup__note--todo" }, "Replace the source list's placeholders first, or the agent goes looking for them.") : null,
4663
- el(
4664
- "button",
4665
- {
4666
- type: "button",
4667
- class: "button button--primary",
4668
- disabled: !agent.enabled || running || state2.restart === "waiting" || placeholders,
4669
- title: !agent.enabled ? "Choose the agent first" : placeholders ? "Replace the source list's placeholders first" : void 0,
4670
- onClick: () => handlers2.runAgent()
4671
- },
4672
- running ? "Running\u2026" : "Run the morning agent now"
4673
- )
4674
- ]
4675
- )
4676
- )
4677
- );
4678
- }
4679
- function step(done, title, body) {
4680
- return el(
4681
- "li",
4682
- { class: "setup__step", "data-done": done === null ? void 0 : String(done) },
4683
- el(
4684
- "span",
4685
- { class: "setup__mark", "aria-label": done === null ? void 0 : done ? "Done" : "To do" },
4686
- done === null ? "\xB7" : done ? "\u2713" : "\u25CB"
4687
- ),
4688
- el("div", { class: "setup__body" }, el("h3", { class: "setup__step-title" }, title), body)
4689
- );
4690
- }
4691
- function choices(options) {
4692
- return el(
4693
- "div",
4694
- { class: "setup__choices" },
4695
- options.map(([label, onClick, title]) => el("button", { type: "button", class: "button", key: label, title, onClick }, label))
4696
- );
4697
- }
4698
- function agentChoice(state2, handlers2) {
4699
- const found = ["claude", "codex"].filter((cli) => state2.setup.clis[cli]);
4700
- if (found.length === 0) {
4701
- return [
4702
- el(
4703
- "p",
4704
- null,
4705
- "The brief is written by a coding-agent CLI you already use. Install ",
4706
- el("a", { href: "https://code.claude.com/docs", target: "_blank", rel: "noopener" }, "Claude Code"),
4707
- " or the ",
4708
- el("a", { href: "https://developers.openai.com/codex/cli", target: "_blank", rel: "noopener" }, "Codex CLI"),
4709
- ", log in, and restart the dashboard; it looks for both each time it starts."
4710
- )
5299
+ const last = agent.last;
5300
+ const running = last?.status === "running";
5301
+ const restarting = state2.restart === "waiting";
5302
+ let title;
5303
+ let text;
5304
+ let actions;
5305
+ if (restarting) {
5306
+ title = "Saving your settings";
5307
+ text = "The dashboard restarts to apply them, then starts on your first brief.";
5308
+ actions = [];
5309
+ } else if (!agent.enabled) {
5310
+ title = "Choose who writes your brief";
5311
+ text = "Each morning a coding-agent CLI you already use writes it: Claude Code or Codex. The boards work meanwhile.";
5312
+ actions = [el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.openSettings("agent") }, "Choose in Settings")];
5313
+ } else if (running) {
5314
+ title = "Writing your first brief";
5315
+ 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.";
5316
+ actions = [el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Watch it work")];
5317
+ } else if (last && last.status !== "done") {
5318
+ title = "Your first brief didn\u2019t finish";
5319
+ 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.";
5320
+ actions = [
5321
+ el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Try again"),
5322
+ el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Read its report")
4711
5323
  ];
5324
+ } else if (last) {
5325
+ title = "Your first brief didn\u2019t arrive";
5326
+ text = "The run finished without a brief this dashboard could read. Its report says why.";
5327
+ actions = [
5328
+ el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Run it again"),
5329
+ el("button", { type: "button", class: "button", onClick: () => handlers2.openRun(last.id) }, "Read its report")
5330
+ ];
5331
+ } else {
5332
+ title = "Write your first brief";
5333
+ text = "It takes a few minutes. After that it runs by itself each scheduled morning.";
5334
+ actions = [el("button", { type: "button", class: "button button--primary", onClick: () => handlers2.runAgent() }, "Write it now")];
4712
5335
  }
4713
- return [
4714
- el("p", null, "The dashboard runs a coding-agent CLI you already use, headless, each morning. Found on this machine:"),
4715
- choices(
4716
- found.map(
4717
- (cli) => [`Use ${CLI_LABEL[cli]}`, () => handlers2.chooseSettings({ DAILY_FOCUS_AGENT: cli }), state2.setup.clis[cli] ?? void 0]
4718
- )
4719
- )
4720
- ];
4721
- }
4722
- var AGENT_ADVANCED = "setup:agent-advanced";
4723
- function agentAdvanced(cli, ui2, handlers2) {
4724
- const keys = ["DAILY_FOCUS_AGENT_MODEL", "DAILY_FOCUS_AGENT_EFFORT", ...cli === "claude" ? ["DAILY_FOCUS_AGENT_TOOLS"] : []];
4725
- const page = ui2.settings.value;
4726
- const draft = ui2.settingsDraft.value;
4727
- const changed = keys.some((key) => draft.has(key));
4728
- const error = changed ? ui2.settingsError.value : null;
4729
- return el(
4730
- "details",
4731
- {
4732
- class: "setup__advanced",
4733
- open: ui2.openDrawers.value.has(AGENT_ADVANCED),
4734
- onToggle: (event) => {
4735
- const open = event.currentTarget.open;
4736
- if (open && !ui2.settings.peek()) handlers2.loadSettings();
4737
- handlers2.toggleDrawer(AGENT_ADVANCED, open);
4738
- }
4739
- },
4740
- el("summary", null, cli === "claude" ? "Advanced: model, effort and tools" : "Advanced: model and effort"),
4741
- page ? [
4742
- ...keys.flatMap((key) => {
4743
- const setting = page.settings.find((entry) => entry.key === key);
4744
- return setting ? [settingField(setting, draft, handlers2)] : [];
4745
- }),
4746
- error ? el("p", { class: "setup__note setup__note--todo", role: "alert" }, error) : null,
4747
- el(
4748
- "div",
4749
- { class: "setup__advanced-actions" },
4750
- el(
4751
- "button",
4752
- { type: "button", class: "button", disabled: !changed || ui2.settingsSaving.value, onClick: () => handlers2.saveSettings(keys) },
4753
- ui2.settingsSaving.value ? "Saving\u2026" : "Save"
4754
- )
4755
- )
4756
- ] : el("p", null, "Loading the settings\u2026")
4757
- );
4758
- }
4759
- var SOURCES_EDITOR = "setup:sources";
4760
- function sourcesStep(state2, ui2, handlers2) {
4761
- const sources = state2.setup.sources;
4762
- const open = ui2.openDrawers.value.has(SOURCES_EDITOR);
4763
- const toggle = () => {
4764
- if (!open && !ui2.texts.value.sources) handlers2.loadText("sources");
4765
- handlers2.toggleDrawer(SOURCES_EDITOR, !open);
4766
- };
4767
- return [
4768
- el(
4769
- "p",
4770
- null,
4771
- "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."
4772
- ),
4773
- sources.exists ? el(
4774
- "p",
4775
- { class: sources.placeholders > 0 ? "setup__note setup__note--todo" : "setup__note" },
4776
- sources.placeholders > 0 ? `It still has ${sources.placeholders} of the template's placeholders. Replace them, or delete what doesn't apply.` : "Written."
4777
- ) : el("p", { class: "setup__note" }, "It starts from a template, with what gh and git know about you already filled in."),
4778
- el(
4779
- "button",
4780
- { type: "button", class: "button", "aria-expanded": String(open), onClick: toggle },
4781
- open ? "Close" : sources.exists ? "Edit it" : "Write it"
4782
- ),
4783
- open ? el("div", { class: "setup__editor" }, textEditor("sources", ui2.texts.value.sources, handlers2, "sources.md")) : null
4784
- ];
4785
- }
4786
- var HEALTH_MARK = { ok: "\u2713", checking: "\u2026", problem: "!", todo: "\u25CB" };
4787
- var HEALTH_LABEL = {
4788
- ok: "Connected",
4789
- checking: "Checking",
4790
- problem: "Not working",
4791
- todo: "To do"
4792
- };
4793
- function boardHealth(board, via, service) {
4794
- if (board.reason) return ["problem", board.reason.message];
4795
- if (board.fetchedAt) return ["ok", `Connected, through ${via}.`];
4796
- return ["checking", `Checking ${service} through ${via}\u2026`];
4797
- }
4798
- function boards(state2, handlers2) {
4799
- const rows = [];
4800
- const off = (key, label) => el("button", { type: "button", class: "button", onClick: () => handlers2.chooseSettings({ [key]: "off" }) }, `Turn off ${label}`);
4801
- if (state2.board.enabled) {
4802
- rows.push(boardRow("Pull requests", ...boardHealth(state2.board, "gh", "GitHub"), off("DAILY_FOCUS_GITHUB", "the pull request board")));
4803
- }
4804
- if (state2.tickets.enabled) {
4805
- rows.push(boardRow("Jira tickets", ...boardHealth(state2.tickets, "acli", "Jira"), off("DAILY_FOCUS_JIRA", "the Jira board")));
4806
- }
4807
- const calendar = state2.setup.calendar;
4808
- if (calendar.supported && state2.agendaSource) {
4809
- const live = state2.agendaSource.live;
4810
- 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"];
4811
- rows.push(
4812
- boardRow(
4813
- "Calendar",
4814
- health,
4815
- text,
4816
- calendar.built && calendar.chosen === 0 ? el("button", { type: "button", class: "button", onClick: () => handlers2.openSettings("calendar") }, "Choose calendars") : off("DAILY_FOCUS_CALENDAR", "the live agenda")
4817
- )
4818
- );
4819
- }
4820
- if (rows.length === 0) return el("p", null, "Nothing else is switched on. The boards can be turned on in Settings.");
4821
- return el("ul", { class: "setup__boards" }, rows);
4822
- }
4823
- function boardRow(name, health, text, action) {
4824
5336
  return el(
4825
- "li",
4826
- { class: "setup__board", "data-health": health, key: name },
4827
- el("span", { class: "setup__board-mark", "aria-label": HEALTH_LABEL[health], title: HEALTH_LABEL[health] }, HEALTH_MARK[health]),
4828
- el("span", { class: "setup__board-name" }, name),
4829
- el("span", { class: "setup__board-text" }, text),
4830
- action
5337
+ "section",
5338
+ { class: "first-brief", id: "first-brief", "aria-labelledby": "first-brief-title", "data-state": running ? "running" : "idle" },
5339
+ el("h2", { class: "first-brief__title", id: "first-brief-title" }, title),
5340
+ el("p", { class: "first-brief__text" }, text),
5341
+ actions.length ? el("div", { class: "first-brief__actions" }, actions) : null
4831
5342
  );
4832
5343
  }
4833
5344
 
4834
5345
  // client/today.ts
4835
5346
  function TodayView({ state: state2, ui: ui2, handlers: handlers2 }) {
4836
- if (state2.setup?.needed) return [k(SetupCard, { state: state2, ui: ui2, handlers: handlers2 })];
4837
- return [renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2), renderSections(state2, ui2, handlers2)];
5347
+ if (state2.setup?.needed) return [k(FirstBriefCard, { state: state2, handlers: handlers2 })];
5348
+ return [
5349
+ el("div", { class: "today__masthead" }, renderObjective(state2, handlers2), renderStats(state2), renderHeadline(state2)),
5350
+ renderSections(state2, ui2, handlers2)
5351
+ ];
4838
5352
  }
4839
5353
  function renderObjective(state2, handlers2 = null) {
4840
5354
  const focus = state2.focus;
@@ -4890,15 +5404,15 @@ function focusTimeStat(agenda, now) {
4890
5404
  const tone = minutes === 0 ? "critical" : minutes < 60 ? "critical" : null;
4891
5405
  return stat("Focus time left", minutes === 0 ? "None" : formatDuration(minutes), tone, `Day ends ${agenda.dayEnd}`);
4892
5406
  }
4893
- function objectiveStat(progress) {
4894
- if (!progress) return null;
4895
- if (progress.workingDaysSince === null) {
5407
+ function objectiveStat(progress2) {
5408
+ if (!progress2) return null;
5409
+ if (progress2.workingDaysSince === null) {
4896
5410
  return stat("Since objective progress", "\u2014", null, "Nothing recorded yet");
4897
5411
  }
4898
- const days = progress.workingDaysSince;
5412
+ const days = progress2.workingDaysSince;
4899
5413
  const tone = days === 0 ? "good" : days >= 3 ? "critical" : null;
4900
5414
  const value = days === 0 ? "Today" : days === 1 ? "1 day" : `${days} days`;
4901
- return stat("Since objective progress", value, tone, progress.lastTitle);
5415
+ return stat("Since objective progress", value, tone, progress2.lastTitle);
4902
5416
  }
4903
5417
  function renderHeadline(state2) {
4904
5418
  const headline = state2.brief.headline;
@@ -4953,10 +5467,12 @@ function App({ ui: ui2, handlers: handlers2 }) {
4953
5467
  const view = ui2.view.value;
4954
5468
  const run = current ? detailRun(current, ui2) : null;
4955
5469
  const row = current && !run ? detailRow(current, ui2) : null;
5470
+ if (current?.onboarding) return [k(OnboardingView, { state: current, ui: ui2, handlers: handlers2 }), k(Toast, { ui: ui2, handlers: handlers2 })];
4956
5471
  return [
4957
5472
  el("a", { class: "skip-link", href: "#main" }, "Skip to content"),
4958
- k(Header, { state: current, ui: ui2, handlers: handlers2 }),
4959
- k(Tabs, { state: current, ui: ui2, handlers: handlers2 }),
5473
+ // The band: the masthead's navy runs behind both, and on Today on behind
5474
+ // the objective below (see `.band` in the stylesheet).
5475
+ el("div", { class: "band" }, k(Header, { state: current, ui: ui2, handlers: handlers2 }), k(Tabs, { state: current, ui: ui2, handlers: handlers2 })),
4960
5476
  el("div", { id: "banners", class: "banners" }, current ? renderBanners(current, ui2, handlers2) : null),
4961
5477
  el(
4962
5478
  "main",
@@ -5116,12 +5632,12 @@ function installKeyboard(handlers2) {
5116
5632
  }
5117
5633
  const item = selectedItem();
5118
5634
  if (!item) return;
5119
- const boardRow2 = ui.view.value !== "today";
5635
+ const boardRow = ui.view.value !== "today";
5120
5636
  const status = "status" in item ? item.status : null;
5121
5637
  switch (event.key) {
5122
5638
  case "e":
5123
5639
  event.preventDefault();
5124
- if (!boardRow2 && status === "open") handlers2.onAction(item.id, "done");
5640
+ if (!boardRow && status === "open") handlers2.onAction(item.id, "done");
5125
5641
  break;
5126
5642
  case "s":
5127
5643
  event.preventDefault();
@@ -5129,7 +5645,7 @@ function installKeyboard(handlers2) {
5129
5645
  break;
5130
5646
  case "x":
5131
5647
  event.preventDefault();
5132
- if (!boardRow2 && status === "open") handlers2.onAction(item.id, "dismiss");
5648
+ if (!boardRow && status === "open") handlers2.onAction(item.id, "dismiss");
5133
5649
  break;
5134
5650
  case "n":
5135
5651
  event.preventDefault();
@@ -5159,6 +5675,9 @@ R(k(App, { ui, handlers }), root);
5159
5675
  j3(() => {
5160
5676
  document.body.dataset.view = ui.view.value;
5161
5677
  });
5678
+ j3(() => {
5679
+ document.body.dataset.onboarding = String(Boolean(state.value?.onboarding));
5680
+ });
5162
5681
  j3(() => {
5163
5682
  document.body.dataset.offline = String(ui.connectionError.value !== null);
5164
5683
  });