@mikitasazan/notify 1.13.0 → 1.14.0

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 (2) hide show
  1. package/dist/render.js +84 -14
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -354,12 +354,17 @@ const renderGroup = (g) => [
354
354
  * was the same row the issue card had already lost for the same reason: you
355
355
  * could not read what the card was about without reading two lines.
356
356
  */
357
- const commitRow = (hash, url, title) => {
358
- const linked = fieldLink('Commit', url, hash);
359
- if (linked === null || !title) {
360
- return linked ?? field('Commit', title);
361
- }
362
- return `${linked} ${esc(firstLine(title))}`;
357
+ const commitRow = (hash, title) => {
358
+ // The title is the content — what the commit DID — so it is what the row
359
+ // says. The hash is a pointer, and pointers live in the last block with
360
+ // `Log`, `Check` and `Source`; the owner caught the hash still carrying the
361
+ // link from the middle of the card (31.08.2026): "source, это hash", and on
362
+ // a manual deploy that buried link was the card's ONLY way back to its
363
+ // source while the pointer block sat empty.
364
+ //
365
+ // With no title there is nothing to say but the hash, so it stands here as
366
+ // plain text — the link to it is in the pointer block either way.
367
+ return field('Commit', title ? firstLine(title) : hash);
363
368
  };
364
369
  const bodyQuote = (body) => body ? note(body) : null;
365
370
  /**
@@ -517,7 +522,7 @@ const renderDeploy = (e) => {
517
522
  // The run URL is gone from this line too — it is the `Source:` row now.
518
523
  typeLine(icon, 'Deploy', mechanism(e.workflowName, e.via), undefined, e.status === 'fail' ? 'Fail' : 'OK'),
519
524
  ...twoBlocks([field('Target', e.target), reason('Reason', e.note), field('Still red', e.stillRed ? `day ${e.stillRed}` : null)], [
520
- commitRow(e.commit, e.commitUrl, e.commitTitle),
525
+ commitRow(e.commit, e.commitTitle),
521
526
  fieldPerson('Author', e.commitAuthor),
522
527
  bodyQuote(e.commitBody)
523
528
  ])
@@ -614,7 +619,7 @@ const renderCi = (e) => {
614
619
  // on a nightly card, the same person everywhere else.
615
620
  ...twoBlocks([reason('Reason', e.note), field('Still red', e.stillRed ? `day ${e.stillRed}` : null)], [
616
621
  fieldTelegram('Actor', e.actor),
617
- commitRow(e.commit, e.commitUrl, e.commitTitle),
622
+ commitRow(e.commit, e.commitTitle),
618
623
  fieldPerson('Author', e.commitAuthor),
619
624
  bodyQuote(e.commitBody)
620
625
  ])
@@ -626,7 +631,26 @@ const renderCi = (e) => {
626
631
  // the thing the card is about could not be read without reading three lines.
627
632
  // The action is not repeated in words: the icon carries it, and no two actions
628
633
  // of one type share an icon.
629
- const named = (number, title) => title ? `#${number} ${title}` : `#${number}`;
634
+ // The number left line 2 on 31.08.2026, by the same rule that moved the commit
635
+ // hash the same day: `#347` is a pointer, and every pointer lives in the last
636
+ // block. Line 2 keeps the title, which is what the thing IS; the number comes
637
+ // back as the text of the `Source` link, where it names exactly what opens.
638
+ //
639
+ // With no title there is nothing left to say, so line 2 falls back to the bare
640
+ // type word — `typeLine` already does that on an empty name. The number is not
641
+ // put back here: it would then appear twice on a titled card and once on an
642
+ // untitled one, which is the inconsistency this move exists to remove.
643
+ //
644
+ // With NO url the number stays put, because then the `Source` row does not
645
+ // exist and dropping it here would erase the number from everything a person
646
+ // reads — the card would name a task without saying which. Same law as
647
+ // `fieldLink`: an identifier must not vanish just because the caller passed no
648
+ // address for it.
649
+ const named = (number, title, url) => {
650
+ if (!title)
651
+ return url ? '' : `#${number}`;
652
+ return url ? title : `#${number} ${title}`;
653
+ };
630
654
  // The people come BEFORE the text, and the text comes only when it is the
631
655
  // news. An `assigned` card carries one new fact — who took it — and it used to
632
656
  // sit dead last, under the issue's entire description: the owner read a card
@@ -654,7 +678,7 @@ const prBody = (action, body) => VERDICT.has(action) ? quoted('Review', body) :
654
678
  // old order, title then Author then Assignee then finally the body — "why
655
679
  // does the assignee cut apart what should be inseparable?"
656
680
  const renderPr = (e) => join([
657
- typeLine(iconFor(e), 'PR', named(e.number, e.title)),
681
+ typeLine(iconFor(e), 'PR', named(e.number, e.title, e.url)),
658
682
  prBody(e.action, e.body),
659
683
  e.body ? '' : null,
660
684
  fieldPerson('Author', e.author),
@@ -667,7 +691,7 @@ const renderPr = (e) => join([
667
691
  // lines, which is what the earlier "he already saw it" reasoning was trying
668
692
  // to save — the expandable quote buys the context back without the cost.
669
693
  const renderIssue = (e) => join([
670
- typeLine(iconFor(e), 'Issue', named(e.number, e.title)),
694
+ typeLine(iconFor(e), 'Issue', named(e.number, e.title, e.url)),
671
695
  bodyQuote(e.body),
672
696
  e.body ? '' : null,
673
697
  fieldPerson('Author', e.author),
@@ -855,13 +879,44 @@ const sourceUrl = (e) => {
855
879
  const url = 'url' in e ? e.url : undefined;
856
880
  return wf ?? url;
857
881
  };
882
+ const link = (url, text) => `<a href="${esc(firstLine(url))}">${esc(text)}</a>`;
883
+ /**
884
+ * Everything the card can be traced back to, as one `Source:` row.
885
+ *
886
+ * A card can have more than one honest source — a deploy through Actions has
887
+ * both a run and the commit that triggered it — and one label with two links
888
+ * beside each other reads better than two rows both called `Source`. They are
889
+ * ordered widest first: the run contains the commit, not the other way round.
890
+ */
891
+ const sourceLinks = (e) => {
892
+ const out = [];
893
+ const run = sourceUrl(e);
894
+ const commitUrl = 'commitUrl' in e ? e.commitUrl : undefined;
895
+ const commit = 'commit' in e ? e.commit : undefined;
896
+ // A pull request and an issue are named by their number and by NOTHING else.
897
+ // `pull request #118` says the type a second time — line 2 already opens with
898
+ // `PR:` — and the owner cut it the hour it shipped: "сверху написано PR,
899
+ // зачем здесь ещё писать PR? Здесь можно просто номер и всё."
900
+ //
901
+ // Only these two types have a number, and only for them is the type word
902
+ // redundant: `workflow run` and `commit 9b1fc68` name things line 2 does NOT,
903
+ // so they keep their nouns.
904
+ const numbered = 'number' in e && typeof e.number === 'number' ? `#${e.number}` : null;
905
+ if (run)
906
+ out.push(link(run, numbered ?? SOURCE_NAME[e.type] ?? 'source'));
907
+ // Without a hash there is no text to put on the link but the word itself,
908
+ // and `commit` alone next to `workflow run` says nothing a reader can use.
909
+ if (commitUrl && commit)
910
+ out.push(link(commitUrl, `commit ${firstLine(commit)}`));
911
+ return out;
912
+ };
858
913
  const pointerBlock = (e) => {
859
- const url = sourceUrl(e);
914
+ const sources = sourceLinks(e);
860
915
  const logs = 'logs' in e ? e.logs : undefined;
861
916
  const rows = [
862
917
  fieldCode('Log', logs),
863
918
  fieldCode('Check', e.check),
864
- url ? `<b>Source:</b> <a href="${esc(url)}">${esc(SOURCE_NAME[e.type] ?? 'source')}</a>` : null
919
+ sources.length > 0 ? `<b>Source:</b> ${sources.join(' · ')}` : null
865
920
  ].filter((r) => r !== null);
866
921
  return rows.length > 0 ? `\n\n${rows.join('\n')}` : '';
867
922
  };
@@ -876,8 +931,23 @@ const cutMarker = (e) => {
876
931
  if (e.path) {
877
932
  return '⋯ cut, full text attached';
878
933
  }
934
+ // A bare `⋯ cut` announces a loss and then says nothing about it. The owner
935
+ // met one on a live PR card (31.08.2026) — a description long enough to be
936
+ // clamped — and asked what the three dots even referred to. The card had a
937
+ // `Source` link the whole time: the rest was one tap away and the marker
938
+ // never said so.
939
+ //
940
+ // So the marker NAMES the row that holds the rest, and never repeats its
941
+ // value: it used to print the log path in full, and the pointer block then
942
+ // printed the same path again on the very next line.
943
+ //
944
+ // `below` is literal, not a figure of speech: the marker closes the clamped
945
+ // body and the pointer block is appended after it.
879
946
  const logs = 'logs' in e ? e.logs : undefined;
880
- return logs ? `⋯ cut, full: <code>${esc(logs)}</code>` : '⋯ cut';
947
+ if (logs) {
948
+ return '⋯ cut, full text at Log below';
949
+ }
950
+ return sourceLinks(e).length > 0 ? '⋯ cut, full text at Source below' : '⋯ cut';
881
951
  };
882
952
  /**
883
953
  * Renders an event into finished HTML text, cut to Telegram's limit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikitasazan/notify",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Единая типизированная отправка Telegram-уведомлений (форум-темы, маршрутизация, ретраи) для всех проектов",
5
5
  "type": "module",
6
6
  "license": "MIT",