paperlint 14.5.0 → 14.6.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.
@@ -105,6 +105,7 @@ interface Blocks {
105
105
  readonly situation?: {
106
106
  readonly summary?: string;
107
107
  readonly question: string;
108
+ readonly from?: string;
108
109
  };
109
110
  readonly excerpt?: {
110
111
  readonly file?: string;
@@ -115,10 +116,13 @@ interface Blocks {
115
116
  readonly annotation?: string;
116
117
  readonly caption?: string;
117
118
  readonly mark_label?: string;
119
+ readonly from?: string;
120
+ readonly caption_from?: string;
118
121
  };
119
122
  readonly quote?: {
120
123
  readonly text: string;
121
124
  readonly source?: string;
125
+ readonly from?: string;
122
126
  };
123
127
  readonly terms?: readonly Term[];
124
128
  readonly answer_notes?: Readonly<Record<string, Readonly<Record<string, string>>>>;
@@ -193,6 +197,7 @@ interface Config {
193
197
  readonly practice: readonly Practice[];
194
198
  readonly practice_round?: number;
195
199
  readonly labels?: Readonly<Record<string, string>>;
200
+ readonly sources?: Readonly<Record<string, string>>;
196
201
  readonly lang?: string;
197
202
  readonly translations?: Readonly<Record<string, Translation>>;
198
203
  readonly items: readonly Item[];
@@ -212,8 +217,11 @@ interface Translation {
212
217
  readonly intro?: string;
213
218
  readonly labels?: Readonly<Record<string, string>>;
214
219
  readonly situation_question?: string;
220
+ readonly sources?: Readonly<Record<string, string>>;
215
221
  readonly questions: Readonly<Record<string, QuestionWords>>;
216
222
  }
223
+ /** The colours the page has for tags (`.tag.s0` to `.tag.s4` in page-template.html), in `sources` order. */
224
+ export declare const SOURCE_COLOURS = 5;
217
225
  /**
218
226
  * Every language of the page with its words whole, the translations checked: each covers every
219
227
  * question and answer of the page and names none it does not have, and gives an intro, a reason
@@ -224,6 +232,7 @@ declare function translationsOf(cfg: Config): {
224
232
  [x: string]: {
225
233
  questions: Question[];
226
234
  situation_question?: string;
235
+ sources?: Readonly<Record<string, string>>;
227
236
  title: string;
228
237
  intro: string;
229
238
  labels: {
@@ -287,11 +296,6 @@ declare function payloadOf(cfg: Config, page: ReturnType<typeof itemsOnPage>, de
287
296
  lang?: string;
288
297
  answerers?: readonly string[];
289
298
  min_seconds?: number;
290
- title: string;
291
- intro: string;
292
- labels: {
293
- [x: string]: string;
294
- };
295
299
  groups: {
296
300
  id: string;
297
301
  label: string;
@@ -302,6 +306,12 @@ declare function payloadOf(cfg: Config, page: ReturnType<typeof itemsOnPage>, de
302
306
  label: string;
303
307
  }[];
304
308
  allowed: JsonObject;
309
+ sources?: never;
310
+ title: string;
311
+ intro: string;
312
+ labels: {
313
+ [x: string]: string;
314
+ };
305
315
  } | {
306
316
  questions: readonly Question[];
307
317
  practice: {
@@ -342,6 +352,7 @@ declare function payloadOf(cfg: Config, page: ReturnType<typeof itemsOnPage>, de
342
352
  [x: string]: {
343
353
  questions: Question[];
344
354
  situation_question?: string;
355
+ sources?: Readonly<Record<string, string>>;
345
356
  title: string;
346
357
  intro: string;
347
358
  labels: {
@@ -351,11 +362,60 @@ declare function payloadOf(cfg: Config, page: ReturnType<typeof itemsOnPage>, de
351
362
  };
352
363
  answerers?: readonly string[];
353
364
  min_seconds?: number;
365
+ groups: {
366
+ id: string;
367
+ label: string;
368
+ }[];
369
+ familiarity_question: string;
370
+ familiarity: {
371
+ value: string;
372
+ label: string;
373
+ }[];
374
+ allowed: JsonObject;
375
+ sources?: never;
354
376
  title: string;
355
377
  intro: string;
356
378
  labels: {
357
379
  [x: string]: string;
358
380
  };
381
+ } | {
382
+ questions: readonly Question[];
383
+ practice: {
384
+ round: number;
385
+ items: ({
386
+ rules?: never;
387
+ id: string;
388
+ expected: Readonly<Record<string, string>>;
389
+ why: string;
390
+ } | {
391
+ rules: {
392
+ description?: string;
393
+ family?: string;
394
+ linter: "eslint" | "ruff";
395
+ id: string;
396
+ }[];
397
+ id: string;
398
+ expected: Readonly<Record<string, string>>;
399
+ why: string;
400
+ })[];
401
+ };
402
+ items: ({
403
+ answerers?: readonly string[];
404
+ rules?: never;
405
+ id: string;
406
+ } | {
407
+ answerers?: readonly string[];
408
+ rules: {
409
+ description?: string;
410
+ family?: string;
411
+ linter: "eslint" | "ruff";
412
+ id: string;
413
+ }[];
414
+ id: string;
415
+ })[];
416
+ lang?: string;
417
+ answerers?: readonly string[];
418
+ min_seconds?: number;
359
419
  groups: {
360
420
  id: string;
361
421
  label: string;
@@ -366,6 +426,84 @@ declare function payloadOf(cfg: Config, page: ReturnType<typeof itemsOnPage>, de
366
426
  label: string;
367
427
  }[];
368
428
  allowed: JsonObject;
429
+ sources: {
430
+ id: string;
431
+ label: string;
432
+ }[];
433
+ title: string;
434
+ intro: string;
435
+ labels: {
436
+ [x: string]: string;
437
+ };
438
+ } | {
439
+ questions: readonly Question[];
440
+ practice: {
441
+ round: number;
442
+ items: ({
443
+ rules?: never;
444
+ id: string;
445
+ expected: Readonly<Record<string, string>>;
446
+ why: string;
447
+ } | {
448
+ rules: {
449
+ description?: string;
450
+ family?: string;
451
+ linter: "eslint" | "ruff";
452
+ id: string;
453
+ }[];
454
+ id: string;
455
+ expected: Readonly<Record<string, string>>;
456
+ why: string;
457
+ })[];
458
+ };
459
+ items: ({
460
+ answerers?: readonly string[];
461
+ rules?: never;
462
+ id: string;
463
+ } | {
464
+ answerers?: readonly string[];
465
+ rules: {
466
+ description?: string;
467
+ family?: string;
468
+ linter: "eslint" | "ruff";
469
+ id: string;
470
+ }[];
471
+ id: string;
472
+ })[];
473
+ lang: string;
474
+ translations: {
475
+ [x: string]: {
476
+ questions: Question[];
477
+ situation_question?: string;
478
+ sources?: Readonly<Record<string, string>>;
479
+ title: string;
480
+ intro: string;
481
+ labels: {
482
+ [x: string]: string;
483
+ };
484
+ };
485
+ };
486
+ answerers?: readonly string[];
487
+ min_seconds?: number;
488
+ groups: {
489
+ id: string;
490
+ label: string;
491
+ }[];
492
+ familiarity_question: string;
493
+ familiarity: {
494
+ value: string;
495
+ label: string;
496
+ }[];
497
+ allowed: JsonObject;
498
+ sources: {
499
+ id: string;
500
+ label: string;
501
+ }[];
502
+ title: string;
503
+ intro: string;
504
+ labels: {
505
+ [x: string]: string;
506
+ };
369
507
  };
370
508
  export type Payload = ReturnType<typeof payloadOf>;
371
509
  /**
@@ -412,11 +550,6 @@ export declare function prepareBuild(configPath: string, outDir: string): {
412
550
  lang?: string;
413
551
  answerers?: readonly string[];
414
552
  min_seconds?: number;
415
- title: string;
416
- intro: string;
417
- labels: {
418
- [x: string]: string;
419
- };
420
553
  groups: {
421
554
  id: string;
422
555
  label: string;
@@ -427,6 +560,12 @@ export declare function prepareBuild(configPath: string, outDir: string): {
427
560
  label: string;
428
561
  }[];
429
562
  allowed: JsonObject;
563
+ sources?: never;
564
+ title: string;
565
+ intro: string;
566
+ labels: {
567
+ [x: string]: string;
568
+ };
430
569
  } | {
431
570
  questions: readonly Question[];
432
571
  practice: {
@@ -467,6 +606,7 @@ export declare function prepareBuild(configPath: string, outDir: string): {
467
606
  [x: string]: {
468
607
  questions: Question[];
469
608
  situation_question?: string;
609
+ sources?: Readonly<Record<string, string>>;
470
610
  title: string;
471
611
  intro: string;
472
612
  labels: {
@@ -476,11 +616,60 @@ export declare function prepareBuild(configPath: string, outDir: string): {
476
616
  };
477
617
  answerers?: readonly string[];
478
618
  min_seconds?: number;
619
+ groups: {
620
+ id: string;
621
+ label: string;
622
+ }[];
623
+ familiarity_question: string;
624
+ familiarity: {
625
+ value: string;
626
+ label: string;
627
+ }[];
628
+ allowed: JsonObject;
629
+ sources?: never;
479
630
  title: string;
480
631
  intro: string;
481
632
  labels: {
482
633
  [x: string]: string;
483
634
  };
635
+ } | {
636
+ questions: readonly Question[];
637
+ practice: {
638
+ round: number;
639
+ items: ({
640
+ rules?: never;
641
+ id: string;
642
+ expected: Readonly<Record<string, string>>;
643
+ why: string;
644
+ } | {
645
+ rules: {
646
+ description?: string;
647
+ family?: string;
648
+ linter: "eslint" | "ruff";
649
+ id: string;
650
+ }[];
651
+ id: string;
652
+ expected: Readonly<Record<string, string>>;
653
+ why: string;
654
+ })[];
655
+ };
656
+ items: ({
657
+ answerers?: readonly string[];
658
+ rules?: never;
659
+ id: string;
660
+ } | {
661
+ answerers?: readonly string[];
662
+ rules: {
663
+ description?: string;
664
+ family?: string;
665
+ linter: "eslint" | "ruff";
666
+ id: string;
667
+ }[];
668
+ id: string;
669
+ })[];
670
+ lang?: string;
671
+ answerers?: readonly string[];
672
+ min_seconds?: number;
484
673
  groups: {
485
674
  id: string;
486
675
  label: string;
@@ -491,6 +680,84 @@ export declare function prepareBuild(configPath: string, outDir: string): {
491
680
  label: string;
492
681
  }[];
493
682
  allowed: JsonObject;
683
+ sources: {
684
+ id: string;
685
+ label: string;
686
+ }[];
687
+ title: string;
688
+ intro: string;
689
+ labels: {
690
+ [x: string]: string;
691
+ };
692
+ } | {
693
+ questions: readonly Question[];
694
+ practice: {
695
+ round: number;
696
+ items: ({
697
+ rules?: never;
698
+ id: string;
699
+ expected: Readonly<Record<string, string>>;
700
+ why: string;
701
+ } | {
702
+ rules: {
703
+ description?: string;
704
+ family?: string;
705
+ linter: "eslint" | "ruff";
706
+ id: string;
707
+ }[];
708
+ id: string;
709
+ expected: Readonly<Record<string, string>>;
710
+ why: string;
711
+ })[];
712
+ };
713
+ items: ({
714
+ answerers?: readonly string[];
715
+ rules?: never;
716
+ id: string;
717
+ } | {
718
+ answerers?: readonly string[];
719
+ rules: {
720
+ description?: string;
721
+ family?: string;
722
+ linter: "eslint" | "ruff";
723
+ id: string;
724
+ }[];
725
+ id: string;
726
+ })[];
727
+ lang: string;
728
+ translations: {
729
+ [x: string]: {
730
+ questions: Question[];
731
+ situation_question?: string;
732
+ sources?: Readonly<Record<string, string>>;
733
+ title: string;
734
+ intro: string;
735
+ labels: {
736
+ [x: string]: string;
737
+ };
738
+ };
739
+ };
740
+ answerers?: readonly string[];
741
+ min_seconds?: number;
742
+ groups: {
743
+ id: string;
744
+ label: string;
745
+ }[];
746
+ familiarity_question: string;
747
+ familiarity: {
748
+ value: string;
749
+ label: string;
750
+ }[];
751
+ allowed: JsonObject;
752
+ sources: {
753
+ id: string;
754
+ label: string;
755
+ }[];
756
+ title: string;
757
+ intro: string;
758
+ labels: {
759
+ [x: string]: string;
760
+ };
494
761
  };
495
762
  dropped: number;
496
763
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "14.5.0",
3
+ "version": "14.6.0",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ description: Build a phone labelling page published as a claude.ai Artifact with
4
4
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Artifact, ArtifactData]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:46ebfc043d459ff0 compiled from skills/build-label-page/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:c5863cfb5f123f19 compiled from skills/build-label-page/SKILL.md.spec.ts -->
8
8
 
9
9
  # build-label-page — a page the person labels on a phone, and the labels back in the repo
10
10
 
@@ -168,6 +168,16 @@ invents a question or an answer; the ids and `key.json` are the same with or wit
168
168
  [`references/closed-choice-generator.md`](references/closed-choice-generator.md), "A second
169
169
  language for the page's own words".
170
170
 
171
+ ## A source tag above each block
172
+
173
+ When the labeller must tell where a block's text came from (copied from a repository, written by the
174
+ model under test, restated by an earlier labelling model, counted by the script), the page's
175
+ `"sources": {"repo": "From the repository", …}` names the tags, in colour order (at most five), and
176
+ `from` on a `quote`, a `situation` (its summary) or an `excerpt` (`caption_from` for its caption)
177
+ puts one above that block. The build refuses an unknown id, a dead entry and a tag over a text the card
178
+ lacks. [`references/closed-choice-generator.md`](references/closed-choice-generator.md), "A source tag
179
+ above each block".
180
+
171
181
  ## When the page misbehaves
172
182
 
173
183
  Look at what the store holds (`ArtifactData` `list`) before changing anything: the page may be fine
@@ -179,6 +179,16 @@ invents a question or an answer; the ids and \`key.json\` are the same with or w
179
179
  [\`references/closed-choice-generator.md\`](references/closed-choice-generator.md), "A second
180
180
  language for the page's own words".
181
181
 
182
+ ## A source tag above each block
183
+
184
+ When the labeller must tell where a block's text came from (copied from a repository, written by the
185
+ model under test, restated by an earlier labelling model, counted by the script), the page's
186
+ \`"sources": {"repo": "From the repository", …}\` names the tags, in colour order (at most five), and
187
+ \`from\` on a \`quote\`, a \`situation\` (its summary) or an \`excerpt\` (\`caption_from\` for its caption)
188
+ puts one above that block. The build refuses an unknown id, a dead entry and a tag over a text the card
189
+ lacks. [\`references/closed-choice-generator.md\`](references/closed-choice-generator.md), "A source tag
190
+ above each block".
191
+
182
192
  ## When the page misbehaves
183
193
 
184
194
  Look at what the store holds (\`ArtifactData\` \`list\`) before changing anything: the page may be fine
@@ -342,6 +342,54 @@ out, the page is exactly as above.
342
342
  - **Ids and key:** the neutral ids, the store's documents, `key.json` and the export are the same,
343
343
  byte for byte, with or without translations.
344
344
 
345
+ ## A source tag above each block
346
+
347
+ For a labeller who cannot tell where a block's text came from: some of it copied from a repository,
348
+ some written by a model under test, some by an earlier labelling model, some counted by the study's
349
+ script (#400). A small coloured tag above the block says which. All fields are optional; a page
350
+ without `sources` shows no tag, and its config, store documents and key are unchanged.
351
+
352
+ ```jsonc
353
+ {
354
+ // id → the tag's label, in colour order (at most 5 ids: the page has five colours)
355
+ "sources": {
356
+ "repo": "From the repository",
357
+ "ours": "Our labelling model's restatement",
358
+ "model": "Written by the model under test",
359
+ "count": "Counted by our script",
360
+ },
361
+ "items": [
362
+ {
363
+ "quote": { "text": "…", "source": "…", "from": "repo" }, // tags the quote and its source line
364
+ "situation": { "summary": "…", "question": "…", "from": "ours" }, // tags the summary, not the question
365
+ "excerpt": {
366
+ "lines": [],
367
+ "caption": "…",
368
+ "from": "repo",
369
+ "caption_from": "count",
370
+ }, // the box, the caption under it
371
+ },
372
+ ],
373
+ "translations": { "ru": { "sources": { "repo": "Из репозитория" } } },
374
+ }
375
+ ```
376
+
377
+ - **`from`** names an id of `sources`, on `quote`, `situation` and `excerpt`; **`caption_from`**
378
+ on `excerpt` tags the caption. Practice items take the same fields. The question is the page's own
379
+ words and gets no tag.
380
+ - **On the page:** a small uppercase pill directly above the block. Its colour is the id's place in
381
+ `sources` (accent, green, amber, red, grey-blue: tokens the page already has). Each carries
382
+ `data-test="source-tag"` and `data-test-meta` `{"source": "<id>", "block": "quote" | "situation" |
383
+ "excerpt" | "caption"}`.
384
+ - **Refused, by path:** a `from` or `caption_from` with no `sources` on the page; one naming an id not
385
+ in `sources`; one over a text the card does not have (a `caption_from` without a `caption`, a
386
+ situation `from` without a `summary`); a `sources` id no item or practice item names; an empty
387
+ label; more than 5 ids; a `translations.<lang>.sources` id the page's `sources` lack.
388
+ - **Translations:** `translations.<lang>.sources` gives the tag labels in that language. A label it
389
+ leaves out reads as in the page's own language.
390
+ - **A tag is not a blind-page field:** it shows the labeller where text came from. Use it only where
391
+ the study allows the labeller to know; a study that blinds the source must not name one.
392
+
345
393
  ## Two answerers, and a minimum time on screen
346
394
 
347
395
  Both keys are optional and top-level. Left out, the page, its store documents and the export are
@@ -1000,9 +1000,10 @@ async function onV7Page(
1000
1000
  cardOf: (sourceId: string) => Promise<Locator>,
1001
1001
  ) => Promise<void>,
1002
1002
  withTime = false,
1003
+ cfg: Record<string, unknown> = V7_CONFIG,
1003
1004
  ): Promise<void> {
1004
1005
  const { page: html, key } = build(
1005
- setup(name, withTime ? V7_CONFIG : R.omit(V7_CONFIG, ["min_seconds"])),
1006
+ setup(name, withTime ? cfg : R.omit(cfg, ["min_seconds"])),
1006
1007
  );
1007
1008
  const done = ["forbid", "allow", "elsewhere"];
1008
1009
  const page = await (
@@ -1635,3 +1636,128 @@ test(
1635
1636
  },
1636
1637
  BROWSER_TEST_MS,
1637
1638
  );
1639
+
1640
+ const TAG_LABELS = {
1641
+ repo: "From the repository",
1642
+ ours: "Our restatement",
1643
+ model: "Written by the model under test",
1644
+ count: "Counted by our script",
1645
+ };
1646
+ const TAG_LABELS_RU = {
1647
+ repo: "Из репозитория",
1648
+ ours: "Пересказ нашей модели",
1649
+ model: "Написано проверяемой моделью",
1650
+ count: "Посчитано нашим скриптом",
1651
+ };
1652
+ const Raw = z.record(z.string(), z.unknown());
1653
+ /** A card's block as the fixture has it, with more fields. */
1654
+ const withIn = (
1655
+ card: Record<string, unknown>,
1656
+ block: string,
1657
+ more: object,
1658
+ ) => ({
1659
+ ...card,
1660
+ [block]: { ...Raw.parse(card[block]), ...more },
1661
+ });
1662
+ /** The v7 fixture with tags: the docstring card has all four kinds, the call card a quote tag only, the depth card none. */
1663
+ const TAGGED_CONFIG = {
1664
+ ...V7_CONFIG,
1665
+ sources: TAG_LABELS,
1666
+ translations: { ru: { ...Raw.parse(RU_RAW), sources: TAG_LABELS_RU } },
1667
+ items: V7_CONFIG.items.map((raw, n) => {
1668
+ const card = Raw.parse(raw);
1669
+ return n === 0
1670
+ ? withIn(
1671
+ withIn(withIn(card, "quote", { from: "repo" }), "situation", {
1672
+ from: "ours",
1673
+ summary: "A reviewer objected to this line.",
1674
+ }),
1675
+ "excerpt",
1676
+ {
1677
+ from: "model",
1678
+ caption: "Counted over the file.",
1679
+ caption_from: "count",
1680
+ },
1681
+ )
1682
+ : n === 1
1683
+ ? withIn(card, "quote", { from: "ours" })
1684
+ : card;
1685
+ }),
1686
+ };
1687
+ const SourceTag = z.object({ source: z.string(), block: z.string() });
1688
+ /** Each source tag of a card: its values and its text. */
1689
+ const tagsOn = async (card: Locator) =>
1690
+ Promise.all(
1691
+ (await allByKind(card, "source-tag")).map(async (t) => ({
1692
+ ...SourceTag.parse(t.meta),
1693
+ text: await t.loc.textContent(),
1694
+ })),
1695
+ );
1696
+ /** How far a tag's bottom edge is above the top of the block it tags (negative: not above). */
1697
+ const gapAbove = async (tag: Locator, block: Locator) => {
1698
+ const [t, b] = [await tag.boundingBox(), await block.boundingBox()];
1699
+ assert.ok(t !== null && b !== null);
1700
+ return b.y - (t.y + t.height);
1701
+ };
1702
+
1703
+ test(
1704
+ "🔴 each tagged block gets a tag with its source id right above it, the label follows the language, and an untagged card has none",
1705
+ async () => {
1706
+ await onV7Page(
1707
+ "browser-v7-source-tags",
1708
+ async (page, cardOf) => {
1709
+ const docstring = await cardOf("card-docstring");
1710
+ assert.deepEqual(await tagsOn(docstring), [
1711
+ { source: "repo", block: "quote", text: TAG_LABELS.repo },
1712
+ { source: "ours", block: "situation", text: TAG_LABELS.ours },
1713
+ { source: "model", block: "excerpt", text: TAG_LABELS.model },
1714
+ { source: "count", block: "caption", text: TAG_LABELS.count },
1715
+ ]);
1716
+ assert.deepEqual(await tagsOn(await cardOf("card-call")), [
1717
+ { source: "ours", block: "quote", text: TAG_LABELS.ours },
1718
+ ]);
1719
+ // Guards: a card whose blocks name no source has no tag at all.
1720
+ assert.deepEqual(await tagsOn(await cardOf("card-depth")), []);
1721
+ const tags = docstring.getByTestId("source-tag");
1722
+ const gaps = [
1723
+ await gapAbove(tags.nth(0), docstring.getByTestId("quote")),
1724
+ await gapAbove(tags.nth(2), docstring.getByTestId("excerpt-path")),
1725
+ ];
1726
+ // Guards: the tag sits above its block, close to it, never beside or below.
1727
+ assert.ok(
1728
+ gaps.every((g) => g >= 0 && g < 16),
1729
+ JSON.stringify(gaps),
1730
+ );
1731
+ const switchTo = async (lang: string) =>
1732
+ (await allByKind(page, "lang-option"))
1733
+ .find(
1734
+ (o) => z.object({ lang: z.string() }).parse(o.meta).lang === lang,
1735
+ )
1736
+ ?.loc.click();
1737
+ await switchTo("ru");
1738
+ assert.deepEqual(
1739
+ (await tagsOn(await cardOf("card-docstring"))).map((t) => t.text),
1740
+ [
1741
+ TAG_LABELS_RU.repo,
1742
+ TAG_LABELS_RU.ours,
1743
+ TAG_LABELS_RU.model,
1744
+ TAG_LABELS_RU.count,
1745
+ ],
1746
+ );
1747
+ },
1748
+ false,
1749
+ TAGGED_CONFIG,
1750
+ );
1751
+ },
1752
+ BROWSER_TEST_MS,
1753
+ );
1754
+
1755
+ test(
1756
+ "a page without sources draws no source tag",
1757
+ async () => {
1758
+ await onV7Page("browser-v7-no-source-tags", async (page) => {
1759
+ assert.equal(await page.getByTestId("source-tag").count(), 0);
1760
+ });
1761
+ },
1762
+ BROWSER_TEST_MS,
1763
+ );