@d3lm/pr-stats 0.2.14 → 0.2.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.
- package/README.md +5 -3
- package/dist/tui-app.mjs +65 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,11 +125,13 @@ The Desktop notifications row in the settings dialog makes the TUI send a deskto
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
Notifications go through the terminal itself when it supports a notification escape sequence, which covers iTerm2, Kitty, Ghostty, WezTerm, and most VTE-based terminals like GNOME Terminal. The terminal posts the notification under its own notification permission and the sequence travels through SSH, so this path needs no setup. Some terminals only show the banner while their window is unfocused. Terminals without such support get the platform's own command instead, `osascript` on macOS and `notify-send` on Linux. A notification that fails to send reports in the footer, for example when `notify-send` is missing on a Linux machine.
|
|
128
|
+
Notifications go through the terminal itself when it supports a notification escape sequence, which covers iTerm2, Kitty, Ghostty, WezTerm, and most VTE-based terminals like GNOME Terminal. The terminal posts the notification under its own notification permission and the sequence travels through SSH, so this path needs no setup beyond what the terminal asks for. Some terminals only show the banner while their window is unfocused. Terminals without such support get the platform's own command instead, `osascript` on macOS and `notify-send` on Linux. A notification that fails to send reports in the footer, for example when `notify-send` is missing on a Linux machine.
|
|
129
129
|
|
|
130
|
-
The "Notification channel" row picks the path, and the choice persists as `notifyChannel` with the values `auto`, `terminal`, and `
|
|
130
|
+
The "Notification channel" row picks the path, and the choice persists as `notifyChannel` with the values `auto`, `terminal`, `command`, and `bell`. On `auto` the TUI tries the terminal first and falls back to the platform command, `terminal` and `command` force one of those paths, and `bell` rings the terminal bell instead of posting any text. The dialog shows the `command` value as the command's name on this platform. Forcing the command pays off inside editor terminals like the one in VS Code, which render the terminal path as a small in-editor toast instead of a system notification. The bell reaches every terminal, including the ones that swallow notifications, and each terminal turns it into its own signal, a sound, a flashing screen, a bell icon on the tab, or a badge on the dock icon. The "Send test notification" row below the channel names the channel the next send takes and sends a sample notification, so you can check that your desktop displays it before relying on it.
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
Two terminals accept the notification sequence without showing anything, and the test row warns about both. iTerm2 drops it until the profile has "Notification Center Alerts" turned on under Settings → Profiles → Terminal, which is off by default. Behind the "Filter Alerts…" button next to it, "Send escape sequence-generated alerts" has to stay on as well, and macOS asks for iTerm2's notification permission on the first send. iTerm2 prefixes the banner with the session name unless its advanced setting "Omit session identifier from notifications" is on. Apple Terminal never implemented the sequence at all, so `auto` skips the terminal there and goes straight to the platform command.
|
|
133
|
+
|
|
134
|
+
The macOS command posts through the built-in Script Editor, and since macOS 15 those notifications stay invisible until Script Editor holds notification permission, without any prompt appearing. To grant it once, open the Script Editor app, run the one-line script `display notification "test"`, and allow the prompt that appears. Script Editor then shows up under Notifications in the System Settings like any other app.
|
|
133
135
|
|
|
134
136
|
## Theming
|
|
135
137
|
|
package/dist/tui-app.mjs
CHANGED
|
@@ -279,7 +279,7 @@ function percentile(sorted, percent) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
// src/settings.ts
|
|
282
|
-
var NOTIFY_CHANNELS = ["auto", "terminal", "command"];
|
|
282
|
+
var NOTIFY_CHANNELS = ["auto", "terminal", "command", "bell"];
|
|
283
283
|
var DEFAULT_RELOAD_INTERVAL = "10m";
|
|
284
284
|
var MAX_RELOAD_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
285
285
|
function reloadIntervalMs(input) {
|
|
@@ -348,7 +348,7 @@ function loadSettings() {
|
|
|
348
348
|
throw new CliError(`"notifications" in ${settingsFile()} must be true or false`);
|
|
349
349
|
}
|
|
350
350
|
if (settings.notifyChannel !== void 0 && !NOTIFY_CHANNELS.includes(settings.notifyChannel)) {
|
|
351
|
-
throw new CliError(`"notifyChannel" in ${settingsFile()} must be "auto", "terminal", or "
|
|
351
|
+
throw new CliError(`"notifyChannel" in ${settingsFile()} must be "auto", "terminal", "command", or "bell"`);
|
|
352
352
|
}
|
|
353
353
|
current = settings;
|
|
354
354
|
return current;
|
|
@@ -4095,7 +4095,7 @@ var SETTINGS = [
|
|
|
4095
4095
|
key: "notifyChannel",
|
|
4096
4096
|
section: "Notifications",
|
|
4097
4097
|
label: "Notification channel",
|
|
4098
|
-
hint: "auto
|
|
4098
|
+
hint: "auto tries the terminal, then the platform command \xB7 the others force one path \xB7 bell rings the terminal bell"
|
|
4099
4099
|
},
|
|
4100
4100
|
{
|
|
4101
4101
|
key: "testNotification",
|
|
@@ -4169,24 +4169,75 @@ var CACHE_MESSAGES = {
|
|
|
4169
4169
|
|
|
4170
4170
|
// src/tui/utils/notify.ts
|
|
4171
4171
|
import { spawn } from "node:child_process";
|
|
4172
|
+
function notificationBoundary(renderer2) {
|
|
4173
|
+
const raw = renderer2;
|
|
4174
|
+
return {
|
|
4175
|
+
get capabilities() {
|
|
4176
|
+
return renderer2.capabilities;
|
|
4177
|
+
},
|
|
4178
|
+
triggerNotification: (message, title) => renderer2.triggerNotification(message, title),
|
|
4179
|
+
writeOut: (data) => {
|
|
4180
|
+
if (typeof raw.writeOut === "function") {
|
|
4181
|
+
raw.writeOut(data);
|
|
4182
|
+
} else {
|
|
4183
|
+
process.stdout.write(data);
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
var BELL = "\x07";
|
|
4189
|
+
function terminalQuirk(renderer2) {
|
|
4190
|
+
const name = renderer2.capabilities?.terminal.name ?? "";
|
|
4191
|
+
if (/iterm/i.test(name)) {
|
|
4192
|
+
return "iterm";
|
|
4193
|
+
}
|
|
4194
|
+
if (/apple_terminal|terminal\.app/i.test(name)) {
|
|
4195
|
+
return "apple-terminal";
|
|
4196
|
+
}
|
|
4197
|
+
return null;
|
|
4198
|
+
}
|
|
4172
4199
|
function createNotifier(renderer2, channel) {
|
|
4173
4200
|
return (title, body, onError) => {
|
|
4174
|
-
if (channel
|
|
4201
|
+
if (channel === "bell") {
|
|
4202
|
+
renderer2.writeOut(BELL);
|
|
4203
|
+
return;
|
|
4204
|
+
}
|
|
4205
|
+
const tryTerminal = channel === "terminal" || channel === "auto" && terminalQuirk(renderer2) !== "apple-terminal";
|
|
4206
|
+
if (tryTerminal && renderer2.triggerNotification(body, title)) {
|
|
4175
4207
|
return;
|
|
4176
4208
|
}
|
|
4177
4209
|
if (channel === "terminal") {
|
|
4178
|
-
onError("the terminal has no notification support, switch the channel to auto
|
|
4210
|
+
onError("the terminal has no notification support, switch the channel to auto, the platform command, or bell");
|
|
4179
4211
|
return;
|
|
4180
4212
|
}
|
|
4181
4213
|
sendNotification(title, body, onError);
|
|
4182
4214
|
};
|
|
4183
4215
|
}
|
|
4184
4216
|
function notificationChannel(renderer2, channel) {
|
|
4185
|
-
if (channel === "
|
|
4217
|
+
if (channel === "bell" || channel === "terminal") {
|
|
4218
|
+
return channel;
|
|
4219
|
+
}
|
|
4220
|
+
if (channel === "auto" && renderer2.capabilities?.notifications === true && terminalQuirk(renderer2) !== "apple-terminal") {
|
|
4186
4221
|
return "terminal";
|
|
4187
4222
|
}
|
|
4188
4223
|
return notificationTool();
|
|
4189
4224
|
}
|
|
4225
|
+
function notificationCaveat(renderer2, channel) {
|
|
4226
|
+
if (channel === "command" || channel === "bell") {
|
|
4227
|
+
return null;
|
|
4228
|
+
}
|
|
4229
|
+
switch (terminalQuirk(renderer2)) {
|
|
4230
|
+
case "iterm": {
|
|
4231
|
+
return "iTerm2 shows these only once Notification Center Alerts is on under Settings \u203A Profiles \u203A Terminal";
|
|
4232
|
+
}
|
|
4233
|
+
case "apple-terminal": {
|
|
4234
|
+
return channel === "auto" ? "Terminal.app ignores terminal notifications, so auto takes the platform command here" : "Terminal.app ignores terminal notifications \xB7 switch the channel to auto, command, or bell";
|
|
4235
|
+
}
|
|
4236
|
+
default: {
|
|
4237
|
+
return null;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4190
4241
|
function notificationTool() {
|
|
4191
4242
|
switch (process.platform) {
|
|
4192
4243
|
case "darwin": {
|
|
@@ -4273,9 +4324,11 @@ function SettingsModal({
|
|
|
4273
4324
|
onSubmit
|
|
4274
4325
|
}) {
|
|
4275
4326
|
const message = cacheAction === null ? null : CACHE_MESSAGES[cacheAction];
|
|
4276
|
-
const renderer2 = useRenderer2();
|
|
4327
|
+
const renderer2 = notificationBoundary(useRenderer2());
|
|
4277
4328
|
const channelValue = notifyChannel2 === "command" ? notificationTool() ?? "unsupported" : notifyChannel2;
|
|
4278
4329
|
const deliveryValue = notificationChannel(renderer2, notifyChannel2) ?? "unsupported";
|
|
4330
|
+
const caveat = SETTINGS[selected].key === "testNotification" ? notificationCaveat(renderer2, notifyChannel2) : null;
|
|
4331
|
+
const bottomLine = error !== null ? { text: error, fg: theme.error } : message !== null ? { text: message.text, fg: message.warn ? theme.warn : theme.muted } : caveat !== null ? { text: caveat, fg: theme.warn } : { text: SETTINGS[selected].hint, fg: theme.muted };
|
|
4279
4332
|
return /* @__PURE__ */ jsxs11(ModalFrame, { title: "Settings", children: [
|
|
4280
4333
|
SECTIONS2.map((section) => /* @__PURE__ */ jsxs11("box", { flexDirection: "column", marginBottom: 1, children: [
|
|
4281
4334
|
/* @__PURE__ */ jsx12("text", { wrapMode: "none", fg: theme.accent, marginLeft: 2, children: section.title }),
|
|
@@ -4302,17 +4355,7 @@ function SettingsModal({
|
|
|
4302
4355
|
) }, setting.key);
|
|
4303
4356
|
})
|
|
4304
4357
|
] }, section.title)),
|
|
4305
|
-
/* @__PURE__ */ jsx12(
|
|
4306
|
-
"text",
|
|
4307
|
-
{
|
|
4308
|
-
wrapMode: "word",
|
|
4309
|
-
height: 2,
|
|
4310
|
-
fg: error !== null ? theme.error : message?.warn ? theme.warn : theme.muted,
|
|
4311
|
-
marginLeft: 2,
|
|
4312
|
-
marginRight: 2,
|
|
4313
|
-
children: error ?? message?.text ?? SETTINGS[selected].hint
|
|
4314
|
-
}
|
|
4315
|
-
)
|
|
4358
|
+
/* @__PURE__ */ jsx12("text", { wrapMode: "word", height: 2, fg: bottomLine.fg, marginLeft: 2, marginRight: 2, children: bottomLine.text })
|
|
4316
4359
|
] });
|
|
4317
4360
|
}
|
|
4318
4361
|
function SettingValue({
|
|
@@ -6905,7 +6948,10 @@ function App({
|
|
|
6905
6948
|
const [notifyChannel2, setNotifyChannel] = useState4(initialNotifyChannel);
|
|
6906
6949
|
const [copyLinks2, setCopyLinks] = useState4(initialCopyLinks);
|
|
6907
6950
|
const [themeState, setThemeState] = useState4(initialTheme);
|
|
6908
|
-
const defaultNotify = useMemo2(
|
|
6951
|
+
const defaultNotify = useMemo2(
|
|
6952
|
+
() => createNotifier(notificationBoundary(renderer2), notifyChannel2),
|
|
6953
|
+
[renderer2, notifyChannel2]
|
|
6954
|
+
);
|
|
6909
6955
|
const notifier = notify ?? defaultNotify;
|
|
6910
6956
|
const copyRow = (row) => {
|
|
6911
6957
|
copyLink(row.url, (message) => {
|