@mikitasazan/notify 1.8.0 → 1.8.1

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/dist/events.d.ts CHANGED
@@ -1,57 +1,61 @@
1
1
  /**
2
- * Каталог событийединственная точка входа для отправки. `notify()` (см.
3
- * `send.ts`) принимает ТОЛЬКО значения этого типа: свободного текста в API
4
- * нет, значит «своё» сообщение технически не написать.
2
+ * The event catalogue the single entry point for sending. `notify()` (see
3
+ * `send.ts`) accepts ONLY values of this type: there is no free text in the
4
+ * API, so a "custom" message cannot technically be written.
5
5
  *
6
- * Правило эволюции схемы (версии нет и не будет сообщение живёт секунду и
7
- * читается глазами, версионировать нечего):
8
- * - новое поле у СУЩЕСТВУЮЩЕГО типа добавляется ТОЛЬКО опциональным;
9
- * - обязательные поля не добавляются никогда только новый тип события.
10
- * Тогда старый вызывающий код и новый пакет совместимы в обе стороны.
6
+ * The schema evolution rule (there is no version and never will be a
7
+ * message lives one second and is read by eyes, there is nothing to
8
+ * version):
9
+ * - a new field on an EXISTING type is added ONLY as optional;
10
+ * - required fields are never added only a new event type.
11
+ * That keeps old caller code and a new package compatible both ways.
11
12
  */
12
13
  export type Project = 'playhub' | 'one-q' | 'arvent' | 'game-publisher' | 'vault' | 'mac-config' | 'alitools';
13
14
  /**
14
- * Стабильный машинный ключ задачипоследняя строка каждой карточки, вида
15
- * `#ключ` (без названия проекта: карточка и так лежит в форуме своего
16
- * проекта — `targets()` не шлёт её в чужой). По нему дневной разборщик
17
- * сверяет «это 🔴 уже закрыто более поздней карточкой того же ключа?» без
18
- * сравнения человеческих формулировок, которые меняются. Необязателен: без
19
- * него ключ выводится из типа и заголовка (см. `render.ts`), но выведенный
20
- * наследует хрупкость формулировки наши регулярные отправители передают
21
- * его явно. В вывод CLI (stderr, и его же сторож на VPS читает объединённым
22
- * потоком по словам `sent|failed|skipped`) ключ не попадает никогда новое
23
- * слово там ослепило бы сторожа.
15
+ * A stable machine key for the task the last line of every card, shaped
16
+ * as `#key` (with no project name: the card already sits in its own
17
+ * project's forum — `targets()` never sends it to someone else's). The
18
+ * daily parser uses it to check "is this 🔴 already closed by a later card
19
+ * with the same key?" without comparing human wording, which changes. It
20
+ * is optional: without it, the key is derived from the type and the title
21
+ * (see `render.ts`), but a derived one inherits the fragility of wording —
22
+ * our regular senders pass it explicitly. The key never reaches CLI output
23
+ * (stderr, which the VPS watchdog also reads as one combined stream by the
24
+ * words `sent|failed|skipped`) a new word there would blind the
25
+ * watchdog.
24
26
  */
25
27
  /**
26
- * Общая часть любого события. `path` локальный файл, который едет ВМЕСТЕ с
27
- * карточкой: карточка становится подписью к вложению. Отдельного вида `file`
28
- * нет с 25.08.2026 — прогон Arvent слал вердикт и лог двумя карточками про
29
- * одну новость.
28
+ * The common part of any event. `path` is a local file that travels WITH the
29
+ * card: the card becomes a caption on the attachment. There has been no
30
+ * separate `file` type since 25.08.2026 — an Arvent run used to send the
31
+ * verdict and the log as two cards about one piece of news.
30
32
  */
31
33
  type Keyed = {
32
34
  key?: string;
33
- /** Локальный файл; карточка уедет как подпись к нему (лимит подписи 1024). */
35
+ /** A local file; the card goes out as its caption (caption limit 1024). */
34
36
  path?: string;
35
- /** Имя файла в чате; по умолчанию имя из `path`. */
37
+ /** The file's name in the chat; defaults to the name from `path`. */
36
38
  filename?: string;
37
39
  };
38
40
  /**
39
- * Позиция списка внутри сообщения: задача из дайджеста, упавшая проверка,
40
- * замечание. `url` необязателентогда рендерится просто строкой.
41
+ * A list item inside a message: a task from a digest, a failed check, a
42
+ * remark. `url` is optional then it renders as a plain row.
41
43
  */
42
44
  /**
43
- * `label` — необязательный жирный префикс перед `text` (`#243 (overdue)`,
44
- * `#287`) для позиций внутри именованных групп отчёта. Без `label` позиция
45
- * рендерится как обычная нумерованная/маркированная строкатак уже
46
- * работают дайджест-задачи и список выключенных workflow.
45
+ * `label` — an optional bold prefix before `text` (`#243 (overdue)`,
46
+ * `#287`) for items inside a report's named groups. Without `label` an
47
+ * item renders as an ordinary numbered/bulleted row that is already how
48
+ * digest tasks and the list of disabled workflows work.
47
49
  */
48
50
  /**
49
- * `group` — имя блока, под которым позиция встанет. Тот же закон, что у
50
- * `lines` и `stats`: без имени позиция идёт в общий список, как раньше.
51
+ * `group` — the name of the block the item will sit under. The same law
52
+ * `lines` and `stats` follow: without a name the item goes into the
53
+ * general list, as before.
51
54
  *
52
- * Заведено потому, что список импорта смешивал три РАЗНЫЕ вещи в одном
53
- * перечне и различал их значком в начале строки: 🆕 вышло сегодня,
54
- * 🔁 вышло из очереди,не вышло совсем. Значок делал работу заголовка.
55
+ * Introduced because the import list was mixing three DIFFERENT things in
56
+ * one listing and telling them apart by an icon at the start of the row:
57
+ * 🆕 came out today, 🔁 came out of the queue, did not come out at all.
58
+ * The icon was doing a heading's job.
55
59
  */
56
60
  export type Item = {
57
61
  text: string;
@@ -60,55 +64,57 @@ export type Item = {
60
64
  group?: string;
61
65
  };
62
66
  export type NotifyEvent = Keyed & (
63
- /** Выкатка кода на сервер. */
67
+ /** Shipping code to the server. */
64
68
  {
65
69
  type: 'deploy';
66
70
  project: Project;
67
71
  status: 'ok' | 'fail';
68
72
  commit?: string;
69
- /** Ссылка на коммитстрока «коммит» становится кликабельной. */
73
+ /** A link to the commit the "commit" row becomes clickable. */
70
74
  commitUrl?: string;
71
- /** Заголовок коммитарендерится полем `Title:`, тело идёт цитатой ниже. */
75
+ /** The commit's title renders as the `Title:` field, the body follows as a quote. */
72
76
  commitTitle?: string;
73
- /** Тело коммита, если естьта же цитата, что и заголовок. */
77
+ /** The commit's body, if there is one the same quote shape as the title. */
74
78
  commitBody?: string;
75
79
  workflowUrl?: string;
76
- /** Название прогона для видимого текста ссылки (по умолчанию `open`). */
80
+ /** The run's name, for the link's visible text (defaults to `open`). */
77
81
  workflowName?: string;
78
82
  url?: string;
79
83
  /**
80
- * Куда выкатили. Заполнять ТОЛЬКО когда окружений больше одного: у сайтов
81
- * с единственным продом «куда: прод» строка, которую читают глазами и
82
- * ничего из неё не узнают.
84
+ * Where it shipped to. Fill it in ONLY when there is more than one
85
+ * environment: on sites with a single prod, "where: prod" is a row
86
+ * the eye reads and learns nothing from.
83
87
  */
84
88
  target?: string;
85
89
  /**
86
- * Откуда запустили: «вручную с Mac», «GitHub Actions». Вот это как раз
87
- * новость путей выкатки два, они дают разные последствия (ручной идёт
88
- * с ноутбука и переменные берёт из локального .env), и по карточке видно,
89
- * какой сработал.
90
+ * Where it was run from: "manually from the Mac," "GitHub Actions."
91
+ * This is exactly the news there are two deploy paths, they carry
92
+ * different consequences (a manual one runs from the laptop and takes
93
+ * its variables from the local .env), and the card shows which one
94
+ * fired.
90
95
  */
91
96
  via?: string;
92
- /** Пояснение: почему отменён/пропущен прогон после деплоя. */
97
+ /** An explanation: why a post-deploy run was cancelled/skipped. */
93
98
  note?: string;
94
99
  }
95
- /** Регулярная задача по расписанию: импорт игр, бэкап БД, валидатор. */
100
+ /** A recurring scheduled task: a game import, a DB backup, a validator. */
96
101
  | {
97
102
  type: 'job';
98
103
  project: Project;
99
104
  job: string;
100
105
  /**
101
- * `disabled` — задача выключена извне (например GitHub Actions кончил
102
- * бесплатные минуты), не провалилась сама.
106
+ * `disabled` — the task was switched off from outside (GitHub Actions
107
+ * ran out of free minutes, for example), it did not fail on its own.
103
108
  *
104
- * `silent` — задача не отчиталась в срок: она не упала, она вообще не
105
- * подала признаков жизни. Это состояние ЗАДАЧИ, а не отдельный вид
106
- * событияоно жило типом `heartbeat_miss`, и владелец справедливо
107
- * спросил, почему задача по расписанию у него под двумя разными тегами.
108
- * Хуже того: сторож молчания шлёт тот же машинный ключ, что и сама
109
- * задача, так что красная карточка `#heartbeat #daily_import` не
110
- * закрывалась зелёной `#job #daily_import` разборщик ищет пару по
111
- * ПОЛНОМУ тегу. Один поток на задачу это чинит.
109
+ * `silent` — the task did not report in on time: it did not fail, it
110
+ * gave no sign of life at all. This is a state of the TASK, not a
111
+ * separate event type it used to live under the type
112
+ * `heartbeat_miss`, and the owner rightly asked why one scheduled task
113
+ * of his carried two different tags. Worse: the silence watchdog sends
114
+ * the same machine key as the task itself, so a red card
115
+ * `#heartbeat #daily_import` did not get closed by a green
116
+ * `#job #daily_import` the parser looks for a pair by the FULL tag.
117
+ * One stream per task fixes that.
112
118
  */
113
119
  status: 'ok' | 'fail' | 'disabled' | 'silent';
114
120
  /**
@@ -119,22 +125,23 @@ export type NotifyEvent = Keyed & (
119
125
  * finishes the name.
120
126
  */
121
127
  aside?: string;
122
- /** Как часто задача обязана отмечатьсядля `silent` и для возврата из него. */
128
+ /** How often the task owes a check-in for `silent` and for recovering from it. */
123
129
  expected?: string;
124
- /** Когда её видели в последний раз. */
130
+ /** When it was last seen. */
125
131
  lastSeen?: string;
126
132
  /**
127
- * Цифры от отправителя. Третий элемент ИМЯ ГРУППЫ, под которой строка
128
- * встанет. Владелец шесть раз просил группы, и каждый раз отправитель
129
- * уже пытался их изобразить подручным: скобками в ярлыке
130
- * («GA4 users (sum of days)»), значком в начале строки (🆕 против ⚠),
131
- * лишней строкой внизу. Группировать было нечем теперь есть.
133
+ * Numbers from the sender. The third element is the GROUP NAME the
134
+ * row will sit under. The owner asked for groups six times, and every
135
+ * time the sender was already trying to fake them with whatever was
136
+ * at hand: parentheses in the label ("GA4 users (sum of days)"), an
137
+ * icon at the start of the row (🆕 versus ⚠), an extra row at the
138
+ * bottom. There was nothing to group with — now there is.
132
139
  *
133
- * Без третьего элемента строка идёт без заголовка, как раньше: все
134
- * существующие отправители продолжают работать не меняясь.
140
+ * Without the third element the row goes with no heading, as before:
141
+ * every existing sender keeps working unchanged.
135
142
  */
136
143
  stats?: Array<[label: string, value: string | number, group?: string]>;
137
- /** Детали: что именно упало, замечания прогона; у `disabled` — список выключенных процессов (каждый со своей ссылкой). */
144
+ /** Details: exactly what failed, notes from the run; for `disabled` — the list of switched-off processes (each with its own link). */
138
145
  items?: Item[];
139
146
  note?: string;
140
147
  /**
@@ -144,9 +151,10 @@ export type NotifyEvent = Keyed & (
144
151
  */
145
152
  command?: string;
146
153
  /**
147
- * WHAT that command does. The owner, on a bare `rm` in a card: "я сейчас
148
- * введу её и сделаю хуй пойми что, я ж не знаю, что делаю". A command he
149
- * cannot read is one he cannot run, so it never travels alone.
154
+ * WHAT that command does. The owner, on a bare `rm` in a card: "I'm
155
+ * about to type it and do god knows what, I don't even know what I'm
156
+ * doing." A command he cannot read is one he cannot run, so it never
157
+ * travels alone.
150
158
  */
151
159
  commandNote?: string;
152
160
  /**
@@ -156,16 +164,16 @@ export type NotifyEvent = Keyed & (
156
164
  */
157
165
  logs?: string;
158
166
  workflowUrl?: string;
159
- /** Название прогона для видимого текста ссылки (по умолчанию `open`). */
167
+ /** The run's name, for the link's visible text (defaults to `open`). */
160
168
  workflowName?: string;
161
169
  /**
162
- * Запасное имя для ссылки на прогон: половина отправителей шлёт её как
163
- * `--url`. Рендер берёт `workflowUrl ?? url`, так что оба имени работают.
164
- * В новых вызовах предпочитай `workflowUrl` — оно говорит, куда ведёт.
170
+ * A fallback name for the run link: half the senders send it as
171
+ * `--url`. The renderer takes `workflowUrl ?? url`, so both names
172
+ * work. In new calls prefer `workflowUrl` — it says where it leads.
165
173
  */
166
174
  url?: string;
167
175
  }
168
- /** Сводка с цифрами: дневной отчёт, дайджест аналитики. */
176
+ /** A summary with numbers: a daily report, an analytics digest. */
169
177
  | {
170
178
  type: 'report';
171
179
  project: Project;
@@ -177,19 +185,19 @@ export type NotifyEvent = Keyed & (
177
185
  * accepts `--period` for it.
178
186
  */
179
187
  aside?: string;
180
- /** Пусто/не передано, когда используются `groups` — два вида отчёта не смешиваются в одном событии. */
188
+ /** Empty/not passed when `groups` is used the two kinds of report are not mixed in one event. */
181
189
  lines?: Array<[label: string, value: string | number, group?: string]>;
182
190
  /**
183
- * Список позиций со ссылкамидля дайджестов задач, где ценность в
184
- * самих названиях, а не в цифре. Рендерятся отдельным блоком после
185
- * `lines`.
191
+ * A list of items with links for task digests, where the value is
192
+ * in the names themselves, not in a number. Renders as a separate
193
+ * block after `lines`.
186
194
  */
187
195
  items?: Item[];
188
196
  /**
189
- * Именованные группы (доска задач: Ready/In Progress/Not on the
190
- * board; аналитика: Metrics/Links) — каждая со своим заголовком и
191
- * списком позиций. Заменяет `lines`/`items`, когда задан: разные
192
- * отчёты используют либо плоский вид, либо группы, не оба разом.
197
+ * Named groups (a task board: Ready/In Progress/Not on the board;
198
+ * analytics: Metrics/Links) — each with its own heading and list of
199
+ * items. Replaces `lines`/`items` when set: different reports use
200
+ * either the flat form or groups, never both at once.
193
201
  */
194
202
  groups?: Array<{
195
203
  name: string;
@@ -197,18 +205,18 @@ export type NotifyEvent = Keyed & (
197
205
  }>;
198
206
  url?: string;
199
207
  }
200
- /** Итог CI на основной ветке. */
208
+ /** The CI outcome on the main branch. */
201
209
  | {
202
210
  type: 'ci';
203
211
  project: Project;
204
212
  status: 'ok' | 'fail';
205
213
  branch?: string;
206
214
  commit?: string;
207
- /** Ссылка на коммитхэш становится кликабельным. */
215
+ /** A link to the commit the hash becomes clickable. */
208
216
  commitUrl?: string;
209
- /** Заголовок коммита (subject) — отдельное поле `Title:`, не цитата. */
217
+ /** The commit's title (subject) — a separate `Title:` field, not a quote. */
210
218
  commitTitle?: string;
211
- /** Тело коммита (после subject) — та же цитата, что и заголовок. */
219
+ /** The commit's body (after the subject) — the same quote shape as the title. */
212
220
  commitBody?: string;
213
221
  actor?: string;
214
222
  /**
@@ -216,16 +224,17 @@ export type NotifyEvent = Keyed & (
216
224
  * schedule, a manual press. Renders as `Reason:`, same as on deploy.
217
225
  */
218
226
  note?: string;
219
- /** Ссылка на прогон (workflow run) — отдельно от `url` запасной для `workflowUrl`. */
227
+ /** A link to the run (workflow run) — separate from `url`, a fallback for `workflowUrl`. */
220
228
  workflowUrl?: string;
221
- /** Название прогона для видимого текста ссылки (по умолчанию `open`). */
229
+ /** The run's name, for the link's visible text (defaults to `open`). */
222
230
  workflowName?: string;
223
231
  url?: string;
224
232
  }
225
233
  /**
226
- * Событие пул-реквеста. Виды покрывают весь путь PR, потому что владелец
227
- * следит за работой команды по вкладке Ops, а не по почте: почта приходит
228
- * только когда тебя позвали лично, и половина событий в неё не попадает.
234
+ * A pull request event. The kinds cover the whole life of a PR, because
235
+ * the owner follows the team's work through the Ops tab, not through
236
+ * mail: mail only arrives when you were personally pinged, and half of
237
+ * the events never reach it.
229
238
  */
230
239
  | {
231
240
  type: 'pr';
@@ -239,26 +248,26 @@ export type NotifyEvent = Keyed & (
239
248
  reviewer?: string;
240
249
  url?: string;
241
250
  }
242
- /** Событие задачи: заведена, взята в работу, закрыта. */
251
+ /** An issue event: filed, taken up, closed. */
243
252
  | {
244
253
  type: 'issue';
245
254
  project: Project;
246
255
  action: 'opened' | 'assigned' | 'closed';
247
256
  number: number;
248
257
  title: string;
249
- /** Тело задачицитата под полем `Title:`, отдельно от заголовка. */
258
+ /** The issue's body a quote under the `Title:` field, separate from the title. */
250
259
  body?: string;
251
260
  author?: string;
252
261
  assignee?: string;
253
262
  url?: string;
254
263
  }
255
- /** Приложение сломалось прямо сейчас (рантайм-алерт). */
264
+ /** The app is broken right now (a runtime alert). */
256
265
  | {
257
266
  type: 'incident';
258
267
  project: Project;
259
268
  title: string;
260
269
  detail?: string;
261
- /** Локальный путь к логам (не URL — рендерится моноширинным, для копирования, не для клика). */
270
+ /** A local path to the logs (not a URL — renders monospaced, to copy, not to click). */
262
271
  logs?: string;
263
272
  url?: string;
264
273
  }
@@ -298,10 +307,10 @@ export type NotifyEvent = Keyed & (
298
307
  status?: 'fail' | 'ok';
299
308
  }
300
309
  /**
301
- * УСТАРЕЛО с 1.4.2: используйте `job` со статусом `silent`. Тип остаётся,
302
- * потому что сторож молчания живёт на сервере и до выкатки шлёт именно его —
303
- * убрать значит потерять карточку молчания ровно тогда, когда она нужна.
304
- * Новых вызовов не добавлять.
310
+ * DEPRECATED since 1.4.2: use `job` with status `silent`. The type stays,
311
+ * because the silence watchdog lives on the server and sends exactly this
312
+ * one until it is redeployed removing it means losing the silence card
313
+ * exactly when it is needed. Do not add new calls.
305
314
  */
306
315
  | {
307
316
  type: 'heartbeat_miss';
@@ -309,27 +318,28 @@ export type NotifyEvent = Keyed & (
309
318
  job: string;
310
319
  lastSeen?: string;
311
320
  expected?: string;
312
- /** Задача снова отчиталасьтот же тип, зелёная карточка вместо красной, ключ (для сверки) не меняется. */
321
+ /** The task reported in again same type, a green card instead of a red one, the key (for matching) does not change. */
313
322
  recovered?: boolean;
314
- /** Готовое предложение-причина; без него собирается из lastSeen/expected. */
323
+ /** A ready-made reason sentence; without it, one is built from lastSeen/expected. */
315
324
  note?: string;
316
325
  });
317
326
  export type EventType = NotifyEvent['type'];
318
- /** Красное = со звуком. Всё остальное тихо. (Отдельной темы «инциденты» больше нетавария видна в ленте проекта.) */
327
+ /** Red = with sound. Everything else is silent. (There is no separate "incidents" topic any more an incident is visible in the project's feed.) */
319
328
  /**
320
- * Словарь значков. Два закона, и оба поставлены владельцем 25.08.2026:
329
+ * The icon dictionary. Two laws, both set by the owner on 25.08.2026:
321
330
  *
322
- * 1. ВНУТРИ одного тега у каждого слова свой значок. Раньше значков было
323
- * ровно четыре на весь пакет, и `Issue: opened` с `Issue: assigned`
324
- * выглядели одинаково, а у задачи три разных беды fail, disabled,
325
- * silent — были одним и тем же красным кругом.
326
- * 2. МЕЖДУ тегами одинаковый смысл выглядит одинаково. `fail` это 🔴 и в
327
- * выкатке, и в CI, и в задаче; «появилось новое» 🆕 и у задачи на доске,
328
- * и у PR, и у файла.
331
+ * 1. WITHIN one tag every word gets its own icon. There used to be exactly
332
+ * four icons for the whole package, and `Issue: opened` looked the same
333
+ * as `Issue: assigned`, while a task's three different kinds of trouble —
334
+ * fail, disabled, silent — were the same red circle.
335
+ * 2. BETWEEN tags the same meaning looks the same. `fail` is 🔴 whether on a
336
+ * deploy, on CI, or on a task; "something new appeared" is 🆕 on a board
337
+ * task, on a PR, and on a file alike.
329
338
  *
330
- * И третий, который держит первые два честными: у значка фиксированный звук.
331
- * Не у события, не у статуса у значка. Пока звук выводился отдельным
332
- * правилом, `🔴 PR: changes_requested` приходила беззвучно.
339
+ * And a third law that keeps the first two honest: the sound is a fixed
340
+ * property of the icon. Not of the event, not of the status — of the icon.
341
+ * While the sound was a separate rule, `🔴 PR: changes_requested` arrived
342
+ * silent.
333
343
  */
334
344
  export declare const ICON: {
335
345
  readonly ok: "✅";
@@ -353,7 +363,7 @@ export declare const PR_ICON: Record<Extract<NotifyEvent, {
353
363
  export declare const ISSUE_ICON: Record<Extract<NotifyEvent, {
354
364
  type: 'issue';
355
365
  }>['action'], string>;
356
- /** Одно место, где решается значок карточки,и рендер, и звук берут его отсюда. */
366
+ /** One place decides the card's iconboth the render and the sound take it from here. */
357
367
  export declare const iconFor: (e: NotifyEvent) => string;
358
368
  export declare const severity: (e: NotifyEvent) => "info" | "error";
359
369
  export {};
package/dist/events.js CHANGED
@@ -1,29 +1,31 @@
1
1
  /**
2
- * Каталог событийединственная точка входа для отправки. `notify()` (см.
3
- * `send.ts`) принимает ТОЛЬКО значения этого типа: свободного текста в API
4
- * нет, значит «своё» сообщение технически не написать.
2
+ * The event catalogue the single entry point for sending. `notify()` (see
3
+ * `send.ts`) accepts ONLY values of this type: there is no free text in the
4
+ * API, so a "custom" message cannot technically be written.
5
5
  *
6
- * Правило эволюции схемы (версии нет и не будет сообщение живёт секунду и
7
- * читается глазами, версионировать нечего):
8
- * - новое поле у СУЩЕСТВУЮЩЕГО типа добавляется ТОЛЬКО опциональным;
9
- * - обязательные поля не добавляются никогда только новый тип события.
10
- * Тогда старый вызывающий код и новый пакет совместимы в обе стороны.
6
+ * The schema evolution rule (there is no version and never will be a
7
+ * message lives one second and is read by eyes, there is nothing to
8
+ * version):
9
+ * - a new field on an EXISTING type is added ONLY as optional;
10
+ * - required fields are never added only a new event type.
11
+ * That keeps old caller code and a new package compatible both ways.
11
12
  */
12
- /** Красное = со звуком. Всё остальное тихо. (Отдельной темы «инциденты» больше нетавария видна в ленте проекта.) */
13
+ /** Red = with sound. Everything else is silent. (There is no separate "incidents" topic any more an incident is visible in the project's feed.) */
13
14
  /**
14
- * Словарь значков. Два закона, и оба поставлены владельцем 25.08.2026:
15
+ * The icon dictionary. Two laws, both set by the owner on 25.08.2026:
15
16
  *
16
- * 1. ВНУТРИ одного тега у каждого слова свой значок. Раньше значков было
17
- * ровно четыре на весь пакет, и `Issue: opened` с `Issue: assigned`
18
- * выглядели одинаково, а у задачи три разных беды fail, disabled,
19
- * silent — были одним и тем же красным кругом.
20
- * 2. МЕЖДУ тегами одинаковый смысл выглядит одинаково. `fail` это 🔴 и в
21
- * выкатке, и в CI, и в задаче; «появилось новое» 🆕 и у задачи на доске,
22
- * и у PR, и у файла.
17
+ * 1. WITHIN one tag every word gets its own icon. There used to be exactly
18
+ * four icons for the whole package, and `Issue: opened` looked the same
19
+ * as `Issue: assigned`, while a task's three different kinds of trouble —
20
+ * fail, disabled, silent — were the same red circle.
21
+ * 2. BETWEEN tags the same meaning looks the same. `fail` is 🔴 whether on a
22
+ * deploy, on CI, or on a task; "something new appeared" is 🆕 on a board
23
+ * task, on a PR, and on a file alike.
23
24
  *
24
- * И третий, который держит первые два честными: у значка фиксированный звук.
25
- * Не у события, не у статуса у значка. Пока звук выводился отдельным
26
- * правилом, `🔴 PR: changes_requested` приходила беззвучно.
25
+ * And a third law that keeps the first two honest: the sound is a fixed
26
+ * property of the icon. Not of the event, not of the status — of the icon.
27
+ * While the sound was a separate rule, `🔴 PR: changes_requested` arrived
28
+ * silent.
27
29
  */
28
30
  export const ICON = {
29
31
  ok: '✅', // passed, closed, done
@@ -59,7 +61,7 @@ const JOB_ICON = {
59
61
  disabled: ICON.off,
60
62
  silent: ICON.unknown
61
63
  };
62
- /** Одно место, где решается значок карточки,и рендер, и звук берут его отсюда. */
64
+ /** One place decides the card's iconboth the render and the sound take it from here. */
63
65
  export const iconFor = (e) => {
64
66
  switch (e.type) {
65
67
  case 'deploy':