daily-focus 0.1.0 → 0.1.1-dev.10

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/SETUP.md CHANGED
@@ -320,7 +320,8 @@ that runs it, and the calendar helper, so there is nothing else to install, and
320
320
  calendar permission is asked for once, by an app you recognise. It needs a Mac with
321
321
  Apple silicon and macOS 13.5 or newer.
322
322
 
323
- Open `Daily Focus.dmg` and drag the app to Applications, then open it from there.
323
+ Download `Daily-Focus.dmg` from the [latest release](https://github.com/watson/daily-focus/releases/latest),
324
+ open it and drag the app to Applications, then open it from there.
324
325
  It has no window and no Dock icon: look for it in the menu bar.
325
326
 
326
327
  To build it yourself from a checkout:
@@ -329,7 +330,7 @@ To build it yourself from a checkout:
329
330
  npm run app
330
331
  ```
331
332
 
332
- That leaves `Daily Focus.dmg` and the app itself in `macos/build/`. Its log is
333
+ That leaves `Daily-Focus.dmg` and the app itself in `macos/build/`. Its log is
333
334
  `~/Library/Logs/Daily Focus.log`. Everything the dashboard starts runs inside the
334
335
  app as far as macOS is concerned, so a permission prompt from the morning agent's
335
336
  CLI, for Reminders, Messages or your calendars, names Daily Focus, and Full Disk
@@ -8,7 +8,7 @@ import {
8
8
  readArchiveIndex,
9
9
  resolveItems,
10
10
  runContractChecks
11
- } from "./chunk-2PLWVW5T.js";
11
+ } from "./chunk-CQ53JYUU.js";
12
12
  import {
13
13
  BRIEF_REFRESH_GRACE_HOURS,
14
14
  describeSchedule,
@@ -551,7 +551,8 @@ async function readTodaysSessions(config, now) {
551
551
  }
552
552
 
553
553
  // src/store.ts
554
- import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile3 } from "node:fs/promises";
554
+ import { createHash } from "node:crypto";
555
+ import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile3, stat } from "node:fs/promises";
555
556
  import { setTimeout as delay } from "node:timers/promises";
556
557
 
557
558
  // src/agenda.ts
@@ -710,6 +711,16 @@ var Store = class {
710
711
  async ensureDataDir() {
711
712
  await mkdir3(this.config.dataDir, { recursive: true });
712
713
  }
714
+ /**
715
+ * Which store this is, as an opaque fingerprint: its path, and the directory's
716
+ * inode and birth time, so a store deleted and made again at the same path
717
+ * counts as another one. A tab compares it across reconnects; see the client's
718
+ * `checkStore`.
719
+ */
720
+ async fingerprint() {
721
+ const info = await stat(this.config.dataDir);
722
+ return createHash("sha256").update(`${this.config.dataDir}\0${info.ino}\0${info.birthtimeMs}`).digest("hex").slice(0, 16);
723
+ }
713
724
  /**
714
725
  * Read and parse items.json.
715
726
  *
package/dist/cli.js CHANGED
@@ -78,7 +78,7 @@ async function demo(open) {
78
78
  });
79
79
  }
80
80
  async function start(open, onStarted) {
81
- const { runServer } = await import("./server-B4FWZHS2.js");
81
+ const { runServer } = await import("./server-O5GF3TRI.js");
82
82
  await runServer({
83
83
  onStarted: (url) => {
84
84
  onStarted?.(url);
@@ -184,10 +184,10 @@ async function main(argv) {
184
184
  await import("./init-ZJCI2723.js");
185
185
  return;
186
186
  case "audit":
187
- await import("./audit-4AUZVELT.js");
187
+ await import("./audit-TPBQFSL3.js");
188
188
  return;
189
189
  case "seed":
190
- await import("./seed-7DIDKXT7.js");
190
+ await import("./seed-S5ZU6EIO.js");
191
191
  return;
192
192
  case "build-calendar":
193
193
  return buildCalendar();
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-NDWZUZJE.js";
4
4
  import {
5
5
  Store
6
- } from "./chunk-2PLWVW5T.js";
6
+ } from "./chunk-CQ53JYUU.js";
7
7
  import {
8
8
  loadConfig
9
9
  } from "./chunk-G4K24I4M.js";
@@ -16,7 +16,7 @@ import {
16
16
  stopSession,
17
17
  tempPathFor,
18
18
  writeJsonAtomic
19
- } from "./chunk-2PLWVW5T.js";
19
+ } from "./chunk-CQ53JYUU.js";
20
20
  import {
21
21
  CLI_NAMES,
22
22
  LAUNCH_SETTINGS,
@@ -3443,7 +3443,7 @@ var SETTINGS = [
3443
3443
  key: "DAILY_FOCUS_AGENT_MODEL",
3444
3444
  group: "agent",
3445
3445
  label: "Model",
3446
- help: "Passed to the CLI as it is, such as opus. Empty uses the CLI's own default.",
3446
+ help: "Passed to the CLI as it is, such as opus for Claude Code. Empty uses the CLI's own default.",
3447
3447
  kind: "text",
3448
3448
  fallback: always("The CLI's own")
3449
3449
  },
@@ -4132,6 +4132,7 @@ async function startServer(env, options = {}) {
4132
4132
  for (const warning of configWarnings) console.warn(`[daily-focus] ${warning}`);
4133
4133
  const store = new Store(config);
4134
4134
  await store.ensureDataDir();
4135
+ const storeId = await store.fingerprint();
4135
4136
  for (const outcome of await linkStore(config)) {
4136
4137
  if (outcome.result === "linked") console.log(`[daily-focus] linked ${describeLink(config, outcome)}`);
4137
4138
  if (outcome.result === "failed") console.warn(`[daily-focus] could not link ${outcome.name}: ${outcome.detail}`);
@@ -4191,7 +4192,8 @@ async function startServer(env, options = {}) {
4191
4192
  setupWarnings: [...configWarnings, ...linkWarning ? [linkWarning] : []],
4192
4193
  restart,
4193
4194
  setup,
4194
- assetVersion
4195
+ assetVersion,
4196
+ storeId
4195
4197
  };
4196
4198
  }
4197
4199
  async function broadcast(prebuilt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daily-focus",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-dev.10",
4
4
  "description": "An interactive dashboard of what you should focus on today at work",
5
5
  "keywords": [
6
6
  "dashboard",
package/public/app.js CHANGED
@@ -1495,11 +1495,13 @@ function adoptState(next) {
1495
1495
  ui.connectionError.value = null;
1496
1496
  });
1497
1497
  startLocalTick();
1498
+ checkStore(next.storeId);
1498
1499
  checkAssetVersion(next.assetVersion);
1499
1500
  }
1500
1501
  async function refresh() {
1501
1502
  try {
1502
1503
  const next = await fetchState();
1504
+ checkStore(next.storeId);
1503
1505
  checkAssetVersion(next.assetVersion);
1504
1506
  n2(() => {
1505
1507
  state.value = next;
@@ -1524,6 +1526,12 @@ function checkAssetVersion(next) {
1524
1526
  }
1525
1527
  location.reload();
1526
1528
  }
1529
+ var loadedStore = null;
1530
+ function checkStore(next) {
1531
+ if (!next) return;
1532
+ loadedStore ??= next;
1533
+ if (next !== loadedStore) location.reload();
1534
+ }
1527
1535
  var PAST_TENSE = {
1528
1536
  done: "Marked done",
1529
1537
  dismiss: "Dismissed",
@@ -1954,9 +1962,9 @@ var handlers = {
1954
1962
  ui.menuFor.value = null;
1955
1963
  }),
1956
1964
  moveTicket: (key, status, from) => void moveTicket(key, status, from),
1957
- openDetail: (id, field2 = null) => n2(() => {
1965
+ openDetail: (id, field = null) => n2(() => {
1958
1966
  setDetailFor(ui, id);
1959
- ui.focusField.value = field2;
1967
+ ui.focusField.value = field;
1960
1968
  closeMenus();
1961
1969
  }),
1962
1970
  closeDetail: () => setDetailFor(ui, null),
@@ -2035,7 +2043,7 @@ var handlers = {
2035
2043
  ui.settingsDraft.value = /* @__PURE__ */ new Map();
2036
2044
  ui.settingsError.value = null;
2037
2045
  }),
2038
- saveSettings: () => void sendSettings(Object.fromEntries(ui.settingsDraft.value), true),
2046
+ saveSettings: (keys) => void sendSettings(Object.fromEntries([...ui.settingsDraft.value].filter(([key]) => !keys || keys.includes(key))), true),
2039
2047
  chooseSettings: (values) => void sendSettings(values, false),
2040
2048
  loadText: (name) => void loadText(name),
2041
2049
  editText: (name, text) => patchText(name, { draft: text }),
@@ -4203,13 +4211,13 @@ function SettingsView({ state: state2, ui: ui2, handlers: handlers2 }) {
4203
4211
  "section",
4204
4212
  { class: "settings-card", id: `settings-${group.id}`, key: group.id, "aria-labelledby": `settings-${group.id}-title` },
4205
4213
  el("h3", { class: "settings-card__title", id: `settings-${group.id}-title` }, group.title),
4206
- plain.map((setting) => field(setting, draft, handlers2)),
4214
+ plain.map((setting) => settingField(setting, draft, handlers2)),
4207
4215
  group.id === "calendar" ? calendarPicker(state2, ui2, handlers2, page.settings) : null,
4208
4216
  advanced.length > 0 ? el(
4209
4217
  "details",
4210
4218
  { class: "settings-card__more" },
4211
4219
  el("summary", null, "More"),
4212
- advanced.map((setting) => field(setting, draft, handlers2))
4220
+ advanced.map((setting) => settingField(setting, draft, handlers2))
4213
4221
  ) : null
4214
4222
  );
4215
4223
  }) : el("p", { class: "empty" }, "Loading the settings\u2026"),
@@ -4348,7 +4356,7 @@ function flagValue(raw) {
4348
4356
  if (raw === "") return "";
4349
4357
  return ["off", "false", "0", "no"].includes(raw.trim().toLowerCase()) ? "off" : "on";
4350
4358
  }
4351
- function field(setting, draft, handlers2) {
4359
+ function settingField(setting, draft, handlers2) {
4352
4360
  const id = `setting-${setting.key}`;
4353
4361
  const locked = setting.source === "environment";
4354
4362
  const value = shownValue(setting, draft);
@@ -4475,7 +4483,7 @@ function SetupCard({ state: state2, ui: ui2, handlers: handlers2 }) {
4475
4483
  el(
4476
4484
  "p",
4477
4485
  { class: "setup__lede" },
4478
- "Each morning an agent reads your mail, calendar, pull requests and the rest, and writes a short brief of what needs you today. A few things to tell it first. Everything here can be changed later in ",
4486
+ "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 ",
4479
4487
  el("button", { type: "button", class: "link-button", onClick: () => handlers2.openSettings() }, "Settings"),
4480
4488
  "."
4481
4489
  ),
@@ -4509,11 +4517,14 @@ function SetupCard({ state: state2, ui: ui2, handlers: handlers2 }) {
4509
4517
  step(
4510
4518
  agent.cli !== null,
4511
4519
  "Which agent writes the brief?",
4512
- agent.cli ? el(
4513
- "p",
4514
- null,
4515
- `${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."
4516
- ) : agentChoice(state2, handlers2)
4520
+ agent.cli ? [
4521
+ el(
4522
+ "p",
4523
+ null,
4524
+ `${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."
4525
+ ),
4526
+ agentAdvanced(agent.cli, ui2, handlers2)
4527
+ ] : agentChoice(state2, handlers2)
4517
4528
  ),
4518
4529
  step(setup.sources.exists && setup.sources.placeholders === 0, "Who are you, and where should it look?", sourcesStep(state2, ui2, handlers2)),
4519
4530
  step(null, "What else should the dashboard show?", boards(state2, handlers2)),
@@ -4586,6 +4597,43 @@ function agentChoice(state2, handlers2) {
4586
4597
  )
4587
4598
  ];
4588
4599
  }
4600
+ var AGENT_ADVANCED = "setup:agent-advanced";
4601
+ function agentAdvanced(cli, ui2, handlers2) {
4602
+ const keys = ["DAILY_FOCUS_AGENT_MODEL", "DAILY_FOCUS_AGENT_EFFORT", ...cli === "claude" ? ["DAILY_FOCUS_AGENT_TOOLS"] : []];
4603
+ const page = ui2.settings.value;
4604
+ const draft = ui2.settingsDraft.value;
4605
+ const changed = keys.some((key) => draft.has(key));
4606
+ const error = changed ? ui2.settingsError.value : null;
4607
+ return el(
4608
+ "details",
4609
+ {
4610
+ class: "setup__advanced",
4611
+ open: ui2.openDrawers.value.has(AGENT_ADVANCED),
4612
+ onToggle: (event) => {
4613
+ const open = event.currentTarget.open;
4614
+ if (open && !ui2.settings.peek()) handlers2.loadSettings();
4615
+ handlers2.toggleDrawer(AGENT_ADVANCED, open);
4616
+ }
4617
+ },
4618
+ el("summary", null, cli === "claude" ? "Advanced: model, effort and tools" : "Advanced: model and effort"),
4619
+ page ? [
4620
+ ...keys.flatMap((key) => {
4621
+ const setting = page.settings.find((entry) => entry.key === key);
4622
+ return setting ? [settingField(setting, draft, handlers2)] : [];
4623
+ }),
4624
+ error ? el("p", { class: "setup__note setup__note--todo", role: "alert" }, error) : null,
4625
+ el(
4626
+ "div",
4627
+ { class: "setup__advanced-actions" },
4628
+ el(
4629
+ "button",
4630
+ { type: "button", class: "button", disabled: !changed || ui2.settingsSaving.value, onClick: () => handlers2.saveSettings(keys) },
4631
+ ui2.settingsSaving.value ? "Saving\u2026" : "Save"
4632
+ )
4633
+ )
4634
+ ] : el("p", null, "Loading the settings\u2026")
4635
+ );
4636
+ }
4589
4637
  var SOURCES_EDITOR = "setup:sources";
4590
4638
  function sourcesStep(state2, ui2, handlers2) {
4591
4639
  const sources = state2.setup.sources;
package/public/style.css CHANGED
@@ -2317,6 +2317,19 @@ body[data-flyout='open'] .layout__side {
2317
2317
  .setup__editor {
2318
2318
  margin-top: 12px;
2319
2319
  }
2320
+ /* The settings page's own fold and fields, inside a step. */
2321
+ .setup__body .setup__advanced p {
2322
+ margin: 0;
2323
+ font-size: 12px;
2324
+ line-height: 1.45;
2325
+ }
2326
+ .setup__advanced .field {
2327
+ max-width: 480px;
2328
+ }
2329
+ .setup__advanced-actions {
2330
+ display: flex;
2331
+ justify-content: flex-start;
2332
+ }
2320
2333
 
2321
2334
  /* ---------- settings ---------- */
2322
2335
 
@@ -2365,16 +2378,19 @@ body[data-flyout='open'] .layout__side {
2365
2378
  color: var(--serious);
2366
2379
  font-size: 13px;
2367
2380
  }
2368
- .settings-card__more {
2381
+ .settings-card__more,
2382
+ .setup__advanced {
2369
2383
  margin-top: 6px;
2370
2384
  }
2371
- .settings-card__more > summary {
2385
+ .settings-card__more > summary,
2386
+ .setup__advanced > summary {
2372
2387
  cursor: pointer;
2373
2388
  color: var(--ink-2);
2374
2389
  font-size: 13px;
2375
2390
  padding: 4px 0;
2376
2391
  }
2377
- .settings-card__more[open] > summary {
2392
+ .settings-card__more[open] > summary,
2393
+ .setup__advanced[open] > summary {
2378
2394
  margin-bottom: 10px;
2379
2395
  }
2380
2396