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.
- package/dist/skills/build-label-page/scripts/label-page-core.d.ts +277 -10
- package/package.json +1 -1
- package/skills/build-label-page/SKILL.md +11 -1
- package/skills/build-label-page/SKILL.md.spec.ts +10 -0
- package/skills/build-label-page/references/closed-choice-generator.md +48 -0
- package/skills/build-label-page/scripts/label-page-browser.test.ts +127 -1
- package/skills/build-label-page/scripts/label-page-core.js +92 -4
- package/skills/build-label-page/scripts/label-page-core.ts +120 -5
- package/skills/build-label-page/scripts/label-page-sources.test.ts +231 -0
- package/skills/build-label-page/scripts/label-page-translations.test.ts +50 -0
- package/skills/build-label-page/scripts/page-template.html +24 -4
|
@@ -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
|
@@ -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:
|
|
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 ?
|
|
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
|
+
);
|