daily-focus 0.1.1-dev.9 → 0.2.0-dev.13
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 +2 -1
- package/SETUP.md +14 -0
- package/dist/{audit-TPBQFSL3.js → audit-JEJRPW7R.js} +3 -3
- package/dist/{chunk-6VWWJZKJ.js → chunk-BC2W7NND.js} +17 -2
- package/dist/{chunk-CQ53JYUU.js → chunk-ISUDD7OB.js} +1 -1
- package/dist/{chunk-G4K24I4M.js → chunk-Z57EHQZV.js} +1 -1
- package/dist/cli.js +10 -16
- package/dist/{init-ZJCI2723.js → init-YTIXVWOB.js} +2 -2
- package/dist/{seed-S5ZU6EIO.js → seed-C2BLE6P2.js} +3 -3
- package/dist/{server-O5GF3TRI.js → server-UIZFO5EF.js} +9 -5
- package/dist/{service-ILBMIZB7.js → service-YVGOLKYX.js} +2 -2
- package/package.json +5 -1
- package/public/app.js +70 -50
- package/public/style.css +45 -13
package/README.md
CHANGED
|
@@ -41,7 +41,8 @@ daily-focus service
|
|
|
41
41
|
|
|
42
42
|
Or, on a Mac, let the menu bar app keep it running: it starts the dashboard at login,
|
|
43
43
|
shows the brief's age at a glance, and tells you when a new brief arrives. It carries
|
|
44
|
-
everything it needs, Node.js included
|
|
44
|
+
everything it needs, Node.js included, and installs a newer dashboard from its menu
|
|
45
|
+
without a new download of the app. See
|
|
45
46
|
[SETUP.md](SETUP.md#keep-the-dashboard-running).
|
|
46
47
|
|
|
47
48
|
[SETUP.md](SETUP.md) covers each step, the integrations and every setting. The
|
package/SETUP.md
CHANGED
|
@@ -324,6 +324,20 @@ Download `Daily-Focus.dmg` from the [latest release](https://github.com/watson/d
|
|
|
324
324
|
open it and drag the app to Applications, then open it from there.
|
|
325
325
|
It has no window and no Dock icon: look for it in the menu bar.
|
|
326
326
|
|
|
327
|
+
The dashboard updates without a new disk image. **Updates** in the menu shows two
|
|
328
|
+
versions, the app's and the dashboard's, and which releases to take: **Stable
|
|
329
|
+
Releases**, or **Development Builds**, a build of every change as soon as it is
|
|
330
|
+
merged. A few times a day the app asks npm for the newest release, and when it
|
|
331
|
+
finds a newer dashboard, it posts a notification and the menu offers **Install
|
|
332
|
+
Dashboard**. Installing downloads it from the GitHub release, a few hundred
|
|
333
|
+
kilobytes, and runs it only if it is signed by the same developer as the app
|
|
334
|
+
and nothing in it has changed. The dashboard restarts into it once the morning
|
|
335
|
+
agent and the assistant aren't busy. If it doesn't start, the app goes back to the
|
|
336
|
+
one it had and says so. The app itself changes less often; when a release brings a
|
|
337
|
+
new one, or a dashboard that needs one, the menu links to its disk image instead.
|
|
338
|
+
A dashboard that `daily-focus service` or a terminal runs is updated the way it was
|
|
339
|
+
installed, not by the app.
|
|
340
|
+
|
|
327
341
|
To build it yourself from a checkout:
|
|
328
342
|
|
|
329
343
|
```sh
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readArchiveIndex,
|
|
9
9
|
resolveItems,
|
|
10
10
|
runContractChecks
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ISUDD7OB.js";
|
|
12
12
|
import {
|
|
13
13
|
BRIEF_REFRESH_GRACE_HOURS,
|
|
14
14
|
describeSchedule,
|
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
nextRunDate,
|
|
18
18
|
resolveSchedule,
|
|
19
19
|
runsOn
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-Z57EHQZV.js";
|
|
21
21
|
import {
|
|
22
22
|
command
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-BC2W7NND.js";
|
|
24
24
|
import {
|
|
25
25
|
workingMsBetween
|
|
26
26
|
} from "./chunk-EXGTFXNV.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/install.ts
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
|
-
import { resolve, sep } from "node:path";
|
|
4
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
var PACKAGED = import.meta.url.endsWith(".js");
|
|
6
7
|
var ROOT = resolve(import.meta.dirname, "..");
|
|
@@ -13,7 +14,20 @@ function ranDirectly(url) {
|
|
|
13
14
|
const file = fileURLToPath(url);
|
|
14
15
|
return file.endsWith(".ts") && process.argv[1] !== void 0 && resolve(process.argv[1]) === file;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
function packageVersion(root = ROOT) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
20
|
+
} catch {
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function appCalendarApp(execPath, root) {
|
|
25
|
+
const contents = `.app${sep}Contents${sep}`;
|
|
26
|
+
if (dirname(execPath).endsWith(`${contents}Helpers`)) return resolve(dirname(execPath), "Daily Focus Calendar.app");
|
|
27
|
+
if (root.includes(`${contents}Resources${sep}`)) return resolve(root, "..", "..", "Helpers", "Daily Focus Calendar.app");
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
var APP_CALENDAR_APP = appCalendarApp(process.execPath, ROOT);
|
|
17
31
|
var USER_CALENDAR_APP = resolve(homedir(), "Library", "Application Support", "Daily Focus", "Daily Focus Calendar.app");
|
|
18
32
|
|
|
19
33
|
export {
|
|
@@ -22,6 +36,7 @@ export {
|
|
|
22
36
|
IN_NPX_CACHE,
|
|
23
37
|
command,
|
|
24
38
|
ranDirectly,
|
|
39
|
+
packageVersion,
|
|
25
40
|
APP_CALENDAR_APP,
|
|
26
41
|
USER_CALENDAR_APP
|
|
27
42
|
};
|
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
IN_NPX_CACHE,
|
|
4
4
|
ROOT,
|
|
5
|
-
USER_CALENDAR_APP
|
|
6
|
-
|
|
5
|
+
USER_CALENDAR_APP,
|
|
6
|
+
packageVersion
|
|
7
|
+
} from "./chunk-BC2W7NND.js";
|
|
7
8
|
|
|
8
9
|
// src/cli.ts
|
|
9
10
|
import { execFile, spawn } from "node:child_process";
|
|
10
|
-
import {
|
|
11
|
+
import { rmSync } from "node:fs";
|
|
11
12
|
import { cp, mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
12
13
|
import { tmpdir } from "node:os";
|
|
13
14
|
import { join } from "node:path";
|
|
@@ -36,13 +37,6 @@ Options
|
|
|
36
37
|
-h, --help print this
|
|
37
38
|
|
|
38
39
|
Everything else is set up in the dashboard itself.`;
|
|
39
|
-
function version() {
|
|
40
|
-
try {
|
|
41
|
-
return JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version ?? "unknown";
|
|
42
|
-
} catch {
|
|
43
|
-
return "unknown";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
40
|
function openInBrowser(url) {
|
|
47
41
|
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
48
42
|
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
@@ -78,7 +72,7 @@ async function demo(open) {
|
|
|
78
72
|
});
|
|
79
73
|
}
|
|
80
74
|
async function start(open, onStarted) {
|
|
81
|
-
const { runServer } = await import("./server-
|
|
75
|
+
const { runServer } = await import("./server-UIZFO5EF.js");
|
|
82
76
|
await runServer({
|
|
83
77
|
onStarted: (url) => {
|
|
84
78
|
onStarted?.(url);
|
|
@@ -136,7 +130,7 @@ async function main(argv) {
|
|
|
136
130
|
return;
|
|
137
131
|
}
|
|
138
132
|
if (values.version) {
|
|
139
|
-
console.log(
|
|
133
|
+
console.log(packageVersion());
|
|
140
134
|
return;
|
|
141
135
|
}
|
|
142
136
|
if (values.data !== void 0) process.env.DAILY_FOCUS_DATA = values.data;
|
|
@@ -174,20 +168,20 @@ async function main(argv) {
|
|
|
174
168
|
}
|
|
175
169
|
return start(open);
|
|
176
170
|
case "service": {
|
|
177
|
-
const { main: service } = await import("./service-
|
|
171
|
+
const { main: service } = await import("./service-YVGOLKYX.js");
|
|
178
172
|
process.exitCode = await service(values.remove ? ["--remove"] : []);
|
|
179
173
|
return;
|
|
180
174
|
}
|
|
181
175
|
// These three are scripts that do their work as they load, and read their
|
|
182
176
|
// own flags from the command line.
|
|
183
177
|
case "init":
|
|
184
|
-
await import("./init-
|
|
178
|
+
await import("./init-YTIXVWOB.js");
|
|
185
179
|
return;
|
|
186
180
|
case "audit":
|
|
187
|
-
await import("./audit-
|
|
181
|
+
await import("./audit-JEJRPW7R.js");
|
|
188
182
|
return;
|
|
189
183
|
case "seed":
|
|
190
|
-
await import("./seed-
|
|
184
|
+
await import("./seed-C2BLE6P2.js");
|
|
191
185
|
return;
|
|
192
186
|
case "build-calendar":
|
|
193
187
|
return buildCalendar();
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
} from "./chunk-QKT2FAB5.js";
|
|
9
9
|
import {
|
|
10
10
|
loadConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Z57EHQZV.js";
|
|
12
12
|
import {
|
|
13
13
|
PACKAGED,
|
|
14
14
|
command
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-BC2W7NND.js";
|
|
16
16
|
import "./chunk-EXGTFXNV.js";
|
|
17
17
|
|
|
18
18
|
// scripts/init.ts
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-NDWZUZJE.js";
|
|
4
4
|
import {
|
|
5
5
|
Store
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ISUDD7OB.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-Z57EHQZV.js";
|
|
10
10
|
import {
|
|
11
11
|
command
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BC2W7NND.js";
|
|
13
13
|
import "./chunk-EXGTFXNV.js";
|
|
14
14
|
|
|
15
15
|
// scripts/seed.ts
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
stopSession,
|
|
17
17
|
tempPathFor,
|
|
18
18
|
writeJsonAtomic
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ISUDD7OB.js";
|
|
20
20
|
import {
|
|
21
21
|
CLI_NAMES,
|
|
22
22
|
LAUNCH_SETTINGS,
|
|
@@ -30,12 +30,13 @@ import {
|
|
|
30
30
|
readDotEnv,
|
|
31
31
|
resolveSchedule,
|
|
32
32
|
scheduledRunDue
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-Z57EHQZV.js";
|
|
34
34
|
import {
|
|
35
35
|
PACKAGED,
|
|
36
36
|
command,
|
|
37
|
+
packageVersion,
|
|
37
38
|
ranDirectly
|
|
38
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-BC2W7NND.js";
|
|
39
40
|
import {
|
|
40
41
|
laterISO,
|
|
41
42
|
parseISO
|
|
@@ -4006,6 +4007,7 @@ async function hydratePath(env = process.env, platform2 = process.platform, exec
|
|
|
4006
4007
|
|
|
4007
4008
|
// src/server.ts
|
|
4008
4009
|
var PUBLIC_DIR = resolve(fileURLToPath(new URL("../public", import.meta.url)));
|
|
4010
|
+
var VERSION = packageVersion();
|
|
4009
4011
|
var MIME = {
|
|
4010
4012
|
".html": "text/html; charset=utf-8",
|
|
4011
4013
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -4102,13 +4104,15 @@ function configure(sources) {
|
|
|
4102
4104
|
return { config: loadConfig(layerEnv(sources, {})), warnings };
|
|
4103
4105
|
}
|
|
4104
4106
|
}
|
|
4105
|
-
function statusOf(state, config) {
|
|
4107
|
+
function statusOf(state, config, busy) {
|
|
4106
4108
|
const last = state.agentRun.last;
|
|
4107
4109
|
return {
|
|
4110
|
+
version: VERSION,
|
|
4108
4111
|
dataDir: config.dataDir,
|
|
4109
4112
|
profile: config.profile,
|
|
4110
4113
|
setupNeeded: state.setup.needed,
|
|
4111
4114
|
restartPending: state.restart !== null,
|
|
4115
|
+
busy,
|
|
4112
4116
|
brief: {
|
|
4113
4117
|
generatedAt: state.brief.generatedAt,
|
|
4114
4118
|
ageHours: state.brief.ageHours,
|
|
@@ -4692,7 +4696,7 @@ ${entry.source}`)).map(({ title, source }) => ({ title, source })).sort((a, b) =
|
|
|
4692
4696
|
return;
|
|
4693
4697
|
}
|
|
4694
4698
|
if (path === "/api/status" && req.method === "GET") {
|
|
4695
|
-
sendJSON(res, 200, statusOf(await buildState(), config));
|
|
4699
|
+
sendJSON(res, 200, statusOf(await buildState(), config, agent.busy || assistant.busy));
|
|
4696
4700
|
return;
|
|
4697
4701
|
}
|
|
4698
4702
|
if (path === "/api/health" && req.method === "GET") {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Z57EHQZV.js";
|
|
4
4
|
import {
|
|
5
5
|
IN_NPX_CACHE,
|
|
6
6
|
PACKAGED,
|
|
7
7
|
ROOT,
|
|
8
8
|
command,
|
|
9
9
|
ranDirectly
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BC2W7NND.js";
|
|
11
11
|
import "./chunk-EXGTFXNV.js";
|
|
12
12
|
|
|
13
13
|
// scripts/service.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daily-focus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-dev.13",
|
|
4
4
|
"description": "An interactive dashboard of what you should focus on today at work",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dashboard",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=22.18"
|
|
40
40
|
},
|
|
41
|
+
"daily-focus": {
|
|
42
|
+
"appInterface": 1,
|
|
43
|
+
"appSource": "0f59ad2ffabffbfd1054778996c4ee337199c92c"
|
|
44
|
+
},
|
|
41
45
|
"scripts": {
|
|
42
46
|
"start": "node src/server.ts",
|
|
43
47
|
"prestart": "node scripts/build.ts",
|
package/public/app.js
CHANGED
|
@@ -1075,6 +1075,7 @@ function postListCalendars() {
|
|
|
1075
1075
|
// client/state.ts
|
|
1076
1076
|
var VIEWS = ["today", "board", "tickets", "settings"];
|
|
1077
1077
|
var RUN_PREFIX = "run:";
|
|
1078
|
+
var PANEL_PREFIX = "panel:";
|
|
1078
1079
|
var VIEW_KEY = "daily-focus:view";
|
|
1079
1080
|
var UNATTENDED_SEEN_KEY = "daily-focus:unattended-seen";
|
|
1080
1081
|
var AGENT_RUN_SEEN_KEY = "daily-focus:agent-run-seen";
|
|
@@ -2653,6 +2654,7 @@ function renderActions(item, state2, ui2, handlers2) {
|
|
|
2653
2654
|
{
|
|
2654
2655
|
type: "button",
|
|
2655
2656
|
class: "button",
|
|
2657
|
+
"aria-haspopup": "menu",
|
|
2656
2658
|
"aria-expanded": String(ui2.menuFor.value === item.id),
|
|
2657
2659
|
onClick: () => handlers2.toggleMenu(item.id)
|
|
2658
2660
|
},
|
|
@@ -2676,9 +2678,9 @@ function renderSnoozeMenu(item, now, handlers2, { indefinite = true, longRange =
|
|
|
2676
2678
|
presets.push(["Next month", addMonths(now, 1), true], ["Next quarter", addMonths(now, 3), true]);
|
|
2677
2679
|
}
|
|
2678
2680
|
let dateInput = null;
|
|
2679
|
-
return
|
|
2680
|
-
|
|
2681
|
-
{ class: "menu"
|
|
2681
|
+
return k(
|
|
2682
|
+
Menu,
|
|
2683
|
+
{ class: "menu" },
|
|
2682
2684
|
presets.map(
|
|
2683
2685
|
([label, until, dated]) => el(
|
|
2684
2686
|
"button",
|
|
@@ -2727,6 +2729,14 @@ function renderSnoozeMenu(item, now, handlers2, { indefinite = true, longRange =
|
|
|
2727
2729
|
)
|
|
2728
2730
|
);
|
|
2729
2731
|
}
|
|
2732
|
+
function Menu({ class: className, children }) {
|
|
2733
|
+
const ref = A2(null);
|
|
2734
|
+
_2(() => {
|
|
2735
|
+
const node = ref.current;
|
|
2736
|
+
if (node?.showPopover && !node.matches(":popover-open")) node.showPopover();
|
|
2737
|
+
});
|
|
2738
|
+
return el("div", { class: className, role: "menu", popover: "manual", ref }, children);
|
|
2739
|
+
}
|
|
2730
2740
|
function addDays(from, days) {
|
|
2731
2741
|
return localDateKey(new Date(from.getFullYear(), from.getMonth(), from.getDate() + days));
|
|
2732
2742
|
}
|
|
@@ -3069,6 +3079,7 @@ function renderPullActions(row, ui2, handlers2) {
|
|
|
3069
3079
|
type: "button",
|
|
3070
3080
|
class: "button",
|
|
3071
3081
|
title: "Park this until a date",
|
|
3082
|
+
"aria-haspopup": "menu",
|
|
3072
3083
|
"aria-expanded": String(ui2.menuFor.value === row.id),
|
|
3073
3084
|
onClick: () => handlers2.toggleMenu(row.id)
|
|
3074
3085
|
},
|
|
@@ -3303,31 +3314,41 @@ function TicketRowView({ item: row, state: state2, ui: ui2, handlers: handlers2
|
|
|
3303
3314
|
renderTicketMeta(row, state2, handlers2)
|
|
3304
3315
|
),
|
|
3305
3316
|
renderTicketActions(row, ui2, handlers2),
|
|
3306
|
-
ui2.menuFor.value === row.id ? renderSnoozeMenu(row, now, handlers2,
|
|
3317
|
+
ui2.menuFor.value === row.id ? renderSnoozeMenu(row, now, handlers2, PARK_MENU) : null
|
|
3307
3318
|
);
|
|
3308
3319
|
}
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
return el("div", { class: "item__meta item__meta--status" }, el("span", { class: "pill pill--status" }, row.workflowStatus || "\u2014"));
|
|
3313
|
-
}
|
|
3320
|
+
var PARK_MENU = { indefinite: false, longRange: true };
|
|
3321
|
+
function renderTicketControls(row, state2, ui2, handlers2) {
|
|
3322
|
+
const menuKey = `${PANEL_PREFIX}${row.id}`;
|
|
3314
3323
|
return el(
|
|
3315
3324
|
"div",
|
|
3316
|
-
{ class: "
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
type: "button",
|
|
3321
|
-
class: "pill pill--status pill--button",
|
|
3322
|
-
title: `Move ${row.key} to another status`,
|
|
3323
|
-
"aria-expanded": String(ui2.statusFor.value === row.id),
|
|
3324
|
-
onClick: () => handlers2.toggleStatus(row.id)
|
|
3325
|
-
},
|
|
3326
|
-
row.workflowStatus || "\u2014"
|
|
3327
|
-
),
|
|
3328
|
-
ui2.statusFor.value === row.id ? renderStatusMenu(row, offered, ui2, handlers2) : null
|
|
3325
|
+
{ class: "flyout__actions" },
|
|
3326
|
+
statusControl(row, state2, ui2, handlers2, menuKey),
|
|
3327
|
+
flagged(row) ? parkButton(row, ui2, handlers2, menuKey) : null,
|
|
3328
|
+
flagged(row) && ui2.menuFor.value === menuKey ? renderSnoozeMenu(row, new Date(state2.now), handlers2, PARK_MENU) : null
|
|
3329
3329
|
);
|
|
3330
3330
|
}
|
|
3331
|
+
function renderTicketStatus(row, state2, ui2, handlers2) {
|
|
3332
|
+
return el("div", { class: "item__meta item__meta--status" }, statusControl(row, state2, ui2, handlers2, row.id));
|
|
3333
|
+
}
|
|
3334
|
+
function statusControl(row, state2, ui2, handlers2, menuKey) {
|
|
3335
|
+
const offered = ticketStatusOptions(row, state2);
|
|
3336
|
+
if (offered.length === 0) return [el("span", { class: "pill pill--status" }, row.workflowStatus || "\u2014")];
|
|
3337
|
+
const open = ui2.statusFor.value === menuKey;
|
|
3338
|
+
const button = el(
|
|
3339
|
+
"button",
|
|
3340
|
+
{
|
|
3341
|
+
type: "button",
|
|
3342
|
+
class: "pill pill--status pill--button",
|
|
3343
|
+
title: `Move ${row.key} to another status`,
|
|
3344
|
+
"aria-haspopup": "menu",
|
|
3345
|
+
"aria-expanded": String(open),
|
|
3346
|
+
onClick: () => handlers2.toggleStatus(menuKey)
|
|
3347
|
+
},
|
|
3348
|
+
row.workflowStatus || "\u2014"
|
|
3349
|
+
);
|
|
3350
|
+
return open ? [button, renderStatusMenu(row, offered, ui2, handlers2)] : [button];
|
|
3351
|
+
}
|
|
3331
3352
|
function ticketStatusOptions(row, state2) {
|
|
3332
3353
|
const project = /^([A-Za-z][A-Za-z0-9_]*)-\d+$/.exec(row.key.trim())?.[1]?.toUpperCase();
|
|
3333
3354
|
const all = project && state2.tickets?.statuses?.[project] || [];
|
|
@@ -3336,9 +3357,9 @@ function ticketStatusOptions(row, state2) {
|
|
|
3336
3357
|
}
|
|
3337
3358
|
function renderStatusMenu(row, offered, ui2, handlers2) {
|
|
3338
3359
|
const busy = ui2.pending.value.has(row.id);
|
|
3339
|
-
return
|
|
3340
|
-
|
|
3341
|
-
{ class: "menu menu--status"
|
|
3360
|
+
return k(
|
|
3361
|
+
Menu,
|
|
3362
|
+
{ class: "menu menu--status" },
|
|
3342
3363
|
offered.map(
|
|
3343
3364
|
(status) => el(
|
|
3344
3365
|
"button",
|
|
@@ -3380,25 +3401,24 @@ function outOfSyncFlag(row, state2, handlers2) {
|
|
|
3380
3401
|
}
|
|
3381
3402
|
function renderTicketActions(row, ui2, handlers2) {
|
|
3382
3403
|
if (!flagged(row)) return null;
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
{
|
|
3389
|
-
type: "button",
|
|
3390
|
-
class: "button",
|
|
3391
|
-
title: "Park this until a date",
|
|
3392
|
-
"aria-expanded": String(ui2.menuFor.value === row.id),
|
|
3393
|
-
onClick: () => handlers2.toggleMenu(row.id)
|
|
3394
|
-
},
|
|
3395
|
-
"Park"
|
|
3396
|
-
)
|
|
3397
|
-
);
|
|
3398
|
-
} else {
|
|
3399
|
-
buttons.push(el("button", { type: "button", class: "button", onClick: () => handlers2.unpark(row.id) }, "Unpark"));
|
|
3404
|
+
return el("div", { class: "item__actions" }, parkButton(row, ui2, handlers2, row.id));
|
|
3405
|
+
}
|
|
3406
|
+
function parkButton(row, ui2, handlers2, menuKey) {
|
|
3407
|
+
if (row.status !== "open") {
|
|
3408
|
+
return el("button", { type: "button", class: "button", onClick: () => handlers2.unpark(row.id) }, "Unpark");
|
|
3400
3409
|
}
|
|
3401
|
-
return
|
|
3410
|
+
return el(
|
|
3411
|
+
"button",
|
|
3412
|
+
{
|
|
3413
|
+
type: "button",
|
|
3414
|
+
class: "button",
|
|
3415
|
+
title: "Park this until a date",
|
|
3416
|
+
"aria-haspopup": "menu",
|
|
3417
|
+
"aria-expanded": String(ui2.menuFor.value === menuKey),
|
|
3418
|
+
onClick: () => handlers2.toggleMenu(menuKey)
|
|
3419
|
+
},
|
|
3420
|
+
"Park"
|
|
3421
|
+
);
|
|
3402
3422
|
}
|
|
3403
3423
|
|
|
3404
3424
|
// client/chrome.ts
|
|
@@ -3960,7 +3980,7 @@ function RowPanel({ row, state: state2, ui: ui2, handlers: handlers2 }) {
|
|
|
3960
3980
|
const enabled = Boolean(state2.assistant?.enabled);
|
|
3961
3981
|
const entry = state2.assistant?.items?.[row.id] ?? { running: false, sessionId: null, turns: [] };
|
|
3962
3982
|
return [
|
|
3963
|
-
flyoutHeader(facts, handlers2),
|
|
3983
|
+
flyoutHeader(facts, rowControls(row, state2, ui2, handlers2), handlers2),
|
|
3964
3984
|
el(
|
|
3965
3985
|
"div",
|
|
3966
3986
|
{ class: "flyout__body", ref: body, onScroll },
|
|
@@ -3995,7 +4015,6 @@ function describeRow(row) {
|
|
|
3995
4015
|
url: row.url ?? null,
|
|
3996
4016
|
source: "jira",
|
|
3997
4017
|
color: typeColor(row.issueType),
|
|
3998
|
-
status: row.workflowStatus || null,
|
|
3999
4018
|
people: [],
|
|
4000
4019
|
detail: null
|
|
4001
4020
|
};
|
|
@@ -4007,7 +4026,6 @@ function describeRow(row) {
|
|
|
4007
4026
|
url: row.url ?? null,
|
|
4008
4027
|
source: "github",
|
|
4009
4028
|
color: sourceColor("github"),
|
|
4010
|
-
status: null,
|
|
4011
4029
|
people: [],
|
|
4012
4030
|
detail: null
|
|
4013
4031
|
};
|
|
@@ -4018,12 +4036,14 @@ function describeRow(row) {
|
|
|
4018
4036
|
url: row.url ?? null,
|
|
4019
4037
|
source: row.source,
|
|
4020
4038
|
color: sourceColor(row.source),
|
|
4021
|
-
status: null,
|
|
4022
4039
|
people: row.people ?? [],
|
|
4023
4040
|
detail: row.detail ?? null
|
|
4024
4041
|
};
|
|
4025
4042
|
}
|
|
4026
|
-
function
|
|
4043
|
+
function rowControls(row, state2, ui2, handlers2) {
|
|
4044
|
+
return "key" in row && "summary" in row ? renderTicketControls(row, state2, ui2, handlers2) : null;
|
|
4045
|
+
}
|
|
4046
|
+
function flyoutHeader(facts, controls, handlers2) {
|
|
4027
4047
|
const title = facts.ref ? [el("span", { class: "item__ref" }, facts.ref), " ", facts.title] : [facts.title];
|
|
4028
4048
|
return el(
|
|
4029
4049
|
"div",
|
|
@@ -4033,7 +4053,6 @@ function flyoutHeader(facts, handlers2) {
|
|
|
4033
4053
|
{ class: "flyout__where" },
|
|
4034
4054
|
el("span", { class: "item__dot", style: `background:${facts.color}`, "aria-hidden": "true" }),
|
|
4035
4055
|
el("span", { class: "item__source" }, SOURCE_LABEL[facts.source] ?? "Other"),
|
|
4036
|
-
facts.status ? el("span", null, `\xB7 ${facts.status}`) : null,
|
|
4037
4056
|
facts.people.length ? el("span", null, `\xB7 ${facts.people.join(", ")}`) : null
|
|
4038
4057
|
),
|
|
4039
4058
|
el(
|
|
@@ -4051,7 +4070,8 @@ function flyoutHeader(facts, handlers2) {
|
|
|
4051
4070
|
"p",
|
|
4052
4071
|
{ class: "flyout__title" },
|
|
4053
4072
|
facts.url ? el("a", { href: facts.url, target: "_blank", rel: "noopener noreferrer" }, ...title) : title
|
|
4054
|
-
)
|
|
4073
|
+
),
|
|
4074
|
+
controls
|
|
4055
4075
|
);
|
|
4056
4076
|
}
|
|
4057
4077
|
function NoteForm({
|
package/public/style.css
CHANGED
|
@@ -1152,14 +1152,29 @@ body[data-session-active='true'] #today .item:not([data-running='true']):focus-w
|
|
|
1152
1152
|
|
|
1153
1153
|
/* snooze menu */
|
|
1154
1154
|
|
|
1155
|
+
/*
|
|
1156
|
+
* A popover in the top layer (`Menu` in client/items.ts says why), hung from the
|
|
1157
|
+
* button that opened it. Only one menu is ever open, so only one button is
|
|
1158
|
+
* expanded and the one anchor name is never ambiguous. `inset` and `margin`
|
|
1159
|
+
* undo the popover's own centring on the viewport.
|
|
1160
|
+
*/
|
|
1161
|
+
[aria-haspopup='menu'][aria-expanded='true'] {
|
|
1162
|
+
anchor-name: --menu;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1155
1165
|
.menu {
|
|
1156
|
-
position:
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1166
|
+
position: fixed;
|
|
1167
|
+
position-anchor: --menu;
|
|
1168
|
+
inset: auto;
|
|
1169
|
+
top: anchor(bottom);
|
|
1170
|
+
right: anchor(right);
|
|
1171
|
+
margin: 4px 0;
|
|
1172
|
+
/* Above the button instead, when the window has no room left below it. */
|
|
1173
|
+
position-try-fallbacks: flip-block;
|
|
1160
1174
|
/* Wide enough that the date field below still shows its own placeholder. */
|
|
1161
1175
|
min-width: 204px;
|
|
1162
1176
|
padding: 5px;
|
|
1177
|
+
color: var(--ink-2);
|
|
1163
1178
|
background: var(--surface);
|
|
1164
1179
|
border: 1px solid var(--border);
|
|
1165
1180
|
border-radius: var(--radius-sm);
|
|
@@ -1636,10 +1651,11 @@ body[data-view='settings'] .layout {
|
|
|
1636
1651
|
* line up with the other tabs' however wide the display is. The 460px floor is
|
|
1637
1652
|
* what drops it to one column on a narrow window rather than squeezing two.
|
|
1638
1653
|
*
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
1654
|
+
* An absolutely positioned child is still fragmented by the columns, and counts
|
|
1655
|
+
* toward balancing them. `.item__actions` stays inside its card, so it travels
|
|
1656
|
+
* with the card into its column and nothing notices. Anything that hangs below a
|
|
1657
|
+
* card must not be in here at all: the row menus once were, and were split
|
|
1658
|
+
* across both columns and pushed the next section down. They are popovers now.
|
|
1643
1659
|
*/
|
|
1644
1660
|
#tickets .list {
|
|
1645
1661
|
display: block;
|
|
@@ -1836,14 +1852,13 @@ body[data-view='settings'] .layout {
|
|
|
1836
1852
|
}
|
|
1837
1853
|
|
|
1838
1854
|
/*
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1855
|
+
* Hung from the pill's left edge, where the park menu hangs from its button's
|
|
1856
|
+
* right: the pill sits in the card's left gutter, so each menu opens toward the
|
|
1857
|
+
* middle of its own card.
|
|
1842
1858
|
*/
|
|
1843
1859
|
.menu--status {
|
|
1844
|
-
top: 26px;
|
|
1845
1860
|
right: auto;
|
|
1846
|
-
left:
|
|
1861
|
+
left: anchor(left);
|
|
1847
1862
|
min-width: 9rem;
|
|
1848
1863
|
}
|
|
1849
1864
|
|
|
@@ -2073,6 +2088,23 @@ body[data-flyout='open'] .layout__side {
|
|
|
2073
2088
|
text-decoration-color: var(--accent);
|
|
2074
2089
|
}
|
|
2075
2090
|
|
|
2091
|
+
/*
|
|
2092
|
+
* A ticket's status and park, laid out as on its card: the status at the left,
|
|
2093
|
+
* the park at the right, so each menu opens into the panel rather than out over
|
|
2094
|
+
* the list. Under the close button too, so the park lines up with it. A child
|
|
2095
|
+
* combinator, because the park menu's own Set button is a `.button` in here.
|
|
2096
|
+
*/
|
|
2097
|
+
.flyout__actions {
|
|
2098
|
+
grid-column: 1 / -1;
|
|
2099
|
+
display: flex;
|
|
2100
|
+
align-items: center;
|
|
2101
|
+
gap: 8px;
|
|
2102
|
+
margin-top: 4px;
|
|
2103
|
+
}
|
|
2104
|
+
.flyout__actions > .button {
|
|
2105
|
+
margin-left: auto;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2076
2108
|
.flyout__body {
|
|
2077
2109
|
flex: 1;
|
|
2078
2110
|
min-height: 0;
|