paperlint 13.3.2 → 13.5.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 +16 -1
- package/package.json +1 -1
- package/skills/assess-novelty/scripts/candidate_page.test.ts +1 -32
- package/skills/build-label-page/SKILL.md +3 -2
- package/skills/build-label-page/SKILL.md.spec.ts +2 -1
- package/skills/build-label-page/references/checklist.md +4 -2
- package/skills/build-label-page/references/closed-choice-generator.md +21 -4
- package/skills/build-label-page/scripts/label-page-browser.test.ts +187 -0
- package/skills/build-label-page/scripts/label-page-core.js +108 -16
- package/skills/build-label-page/scripts/label-page-core.test.ts +206 -3
- package/skills/build-label-page/scripts/label-page-core.ts +167 -19
- package/skills/build-label-page/scripts/label-page-html.test.ts +60 -0
- package/skills/build-label-page/scripts/label-page.js +6 -1
- package/skills/build-label-page/scripts/label-page.ts +6 -1
- package/skills/build-label-page/scripts/page-template.html +75 -11
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
* "seed": 7, fixes the order; change it and every id changes
|
|
41
41
|
* "groups": {"python": "Python"},
|
|
42
42
|
* "labeller_knows": ["python"], optional: items of other groups are not put on the page
|
|
43
|
+
* "familiarity": false, optional: no "how well do you know" question (approval page)
|
|
43
44
|
* "familiarity_question": "How well do you know {group}?", optional
|
|
44
45
|
* "questions": [{"id": "label", "text": "Is this correct?", "short": "correct?",
|
|
45
46
|
* "options": [{"value": "yes", "label": "Yes", "example": "one real case",
|
|
@@ -52,7 +53,11 @@
|
|
|
52
53
|
* "practice": [{"body": "…", "expected": {"label": "yes"}, "why": "one line"}, …], 3 or more
|
|
53
54
|
* "practice_round": 1, optional; raise it after rewording, the practice reopens
|
|
54
55
|
* "items": [{"source_id": "case-0412-b", "group": "python", "body": "…", "code": "…",
|
|
55
|
-
* "context": "…"
|
|
56
|
+
* "context": "…",
|
|
57
|
+
* "unit": {"id": "a.md:12", "text": "the whole line", "part": 1, "of": 2}}]
|
|
58
|
+
* unit: optional; one requirement of a line that holds
|
|
59
|
+
* several. Every part of the line is an item, in one group;
|
|
60
|
+
* id (never shown) tells two places with the same words apart.
|
|
56
61
|
* }
|
|
57
62
|
*
|
|
58
63
|
* TypeScript, compiled beside itself to `label-page.js` by `npm run build`: a consumer runs the skill
|
|
@@ -90,6 +90,9 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
90
90
|
editing: {}, // item id -> the question id the labeller tapped "Change" on
|
|
91
91
|
mineWhy: {}, // item id -> the reasons this page has just written (ahead of the store)
|
|
92
92
|
draftWhy: {}, // item id -> {question id: text typed and not yet saved}
|
|
93
|
+
whyTimers: {}, // "<item id>|<question id>" -> the pending save of a reason being typed
|
|
94
|
+
whyBoxes: {}, // "<item id>|<question id>" -> the reason box of the last render
|
|
95
|
+
typingIn: null, // the item whose reason box has the keyboard: shown even when complete
|
|
93
96
|
mine: {}, // item/group key -> the answers this page has just written (ahead of the store)
|
|
94
97
|
inflight: {}, // item/group key -> writes not yet confirmed
|
|
95
98
|
failed: {}, // item/group key -> error code of the last refused write
|
|
@@ -99,6 +102,7 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
99
102
|
};
|
|
100
103
|
var db = null;
|
|
101
104
|
var chains = {};
|
|
105
|
+
var WHY_PAUSE_MS = 800;
|
|
102
106
|
|
|
103
107
|
try { if (localStorage.getItem("label-filter") === "next") state.filter = "next"; } catch (e) {}
|
|
104
108
|
|
|
@@ -142,7 +146,11 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
142
146
|
}).filter(function (q) { return q !== null; });
|
|
143
147
|
}
|
|
144
148
|
function itemDone(id) { return missingFor(id).length === 0; }
|
|
149
|
+
// A page built with "familiarity": false asks no group anything (an approval page): every group
|
|
150
|
+
// counts as rated, and neither the question nor "how well you know it" is ever shown.
|
|
151
|
+
var ASKS_FAMILIARITY = DATA.familiarity.length > 0;
|
|
145
152
|
function familiarity(g) {
|
|
153
|
+
if (!ASKS_FAMILIARITY) return null;
|
|
146
154
|
var d = state.mine["group:" + g] || state.groups[g];
|
|
147
155
|
return d && valid(DATA.allowed.familiarity, d.familiarity) ? d.familiarity : undefined;
|
|
148
156
|
}
|
|
@@ -197,6 +205,8 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
197
205
|
}
|
|
198
206
|
// A reason saves when the labeller leaves the box (the text equivalent of a tap).
|
|
199
207
|
function setReason(id, qid, text) {
|
|
208
|
+
clearTimeout(state.whyTimers[id + "|" + qid]);
|
|
209
|
+
delete state.whyTimers[id + "|" + qid];
|
|
200
210
|
if (!canWrite()) return;
|
|
201
211
|
var why = Object.assign({}, reasonsOf(id));
|
|
202
212
|
why[qid] = text;
|
|
@@ -302,6 +312,11 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
302
312
|
card.appendChild(rich("p", "body", item.body));
|
|
303
313
|
if (item.code) card.appendChild(codeBlock(item.code));
|
|
304
314
|
if (item.context) card.appendChild(rich("p", "muted", item.context));
|
|
315
|
+
// One requirement of a line that holds several: which one, and the whole line it came from.
|
|
316
|
+
if (item.unit) {
|
|
317
|
+
card.appendChild(el("p", "muted", "From the line (requirement " + item.unit.part + " of " + item.unit.of + "):"));
|
|
318
|
+
card.appendChild(rich("p", "quote", item.unit.text));
|
|
319
|
+
}
|
|
305
320
|
var a = answersOf(item.id);
|
|
306
321
|
askOne(card, a, state.editing[item.id], function (q, v) {
|
|
307
322
|
delete state.editing[item.id];
|
|
@@ -315,8 +330,16 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
315
330
|
var draft = draftOf(item.id, q.id);
|
|
316
331
|
box.value = draft !== undefined ? draft : reasonsOf(item.id)[q.id] || "";
|
|
317
332
|
box.disabled = off;
|
|
318
|
-
// Every keystroke is kept as a draft, so a render from the store never erases unsaved text
|
|
319
|
-
|
|
333
|
+
// Every keystroke is kept as a draft, so a render from the store never erases unsaved text,
|
|
334
|
+
// and a pause in typing saves it: a reason that waits for the box to lose focus is lost when
|
|
335
|
+
// the labeller leaves the page from inside the box (measured: the store kept the drop alone).
|
|
336
|
+
box.addEventListener("input", function () {
|
|
337
|
+
keepDraft(item.id, q.id, box.value);
|
|
338
|
+
var k = item.id + "|" + q.id;
|
|
339
|
+
clearTimeout(state.whyTimers[k]);
|
|
340
|
+
state.whyTimers[k] = setTimeout(function () { setReason(item.id, q.id, box.value); }, WHY_PAUSE_MS);
|
|
341
|
+
});
|
|
342
|
+
state.whyBoxes[item.id + "|" + q.id] = box;
|
|
320
343
|
box.addEventListener("change", function () { setReason(item.id, q.id, box.value); });
|
|
321
344
|
card.appendChild(box);
|
|
322
345
|
});
|
|
@@ -407,20 +430,56 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
407
430
|
function groupBlock(g, off, onlyOpen) {
|
|
408
431
|
var sec = el("section");
|
|
409
432
|
sec.appendChild(el("h2", "", g.label));
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
433
|
+
if (ASKS_FAMILIARITY) {
|
|
434
|
+
var fam = el("div", "ask");
|
|
435
|
+
fam.appendChild(el("p", "q", DATA.familiarity_question.replace("{group}", g.label)));
|
|
436
|
+
fam.appendChild(options(DATA.familiarity, familiarity(g.id), function (v) { setFamiliarity(g.id, v); }, off));
|
|
437
|
+
var gs = state.groups[g.id];
|
|
438
|
+
fam.appendChild(statusLine("group:" + g.id,
|
|
439
|
+
familiarity(g.id) === undefined ? [{ text: "an answer" }] : [], gs && gs.savedAt));
|
|
440
|
+
sec.appendChild(fam);
|
|
441
|
+
}
|
|
417
442
|
groupItems(g.id).forEach(function (i) {
|
|
418
|
-
if (onlyOpen && itemDone(i.id)) return;
|
|
443
|
+
if (onlyOpen && itemDone(i.id) && i.id !== state.typingIn) return;
|
|
419
444
|
sec.appendChild(itemCard(i, off));
|
|
420
445
|
});
|
|
421
446
|
return sec;
|
|
422
447
|
}
|
|
448
|
+
// Every reason typed and not yet saved goes to the store now: the page is being left.
|
|
449
|
+
function flushReasons() {
|
|
450
|
+
Object.keys(state.whyTimers).forEach(function (k) {
|
|
451
|
+
var at = k.indexOf("|"), id = k.slice(0, at), qid = k.slice(at + 1);
|
|
452
|
+
var draft = draftOf(id, qid);
|
|
453
|
+
if (draft === undefined) { clearTimeout(state.whyTimers[k]); delete state.whyTimers[k]; return; }
|
|
454
|
+
setReason(id, qid, draft);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
// The page is rebuilt on every store snapshot; the reason box being typed in gets the keyboard back,
|
|
458
|
+
// with its caret where it was, or the rest of the sentence goes nowhere.
|
|
459
|
+
// Chrome fires "change" on a focused box while a render is removing it, and that change saves and
|
|
460
|
+
// renders: a render started inside a render would paint with no box focused and hide the item being
|
|
461
|
+
// typed in. It is skipped; the write it started renders again when the store confirms it.
|
|
462
|
+
var painting = false;
|
|
423
463
|
function render() {
|
|
464
|
+
if (painting) return;
|
|
465
|
+
painting = true;
|
|
466
|
+
try { renderOnce(); } finally { painting = false; }
|
|
467
|
+
}
|
|
468
|
+
function renderOnce() {
|
|
469
|
+
var typing = null;
|
|
470
|
+
Object.keys(state.whyBoxes).forEach(function (k) {
|
|
471
|
+
var b = state.whyBoxes[k];
|
|
472
|
+
if (document.activeElement === b) typing = { key: k, start: b.selectionStart, end: b.selectionEnd };
|
|
473
|
+
});
|
|
474
|
+
state.whyBoxes = {};
|
|
475
|
+
// Under "Next unanswered" the save that completes an item would hide it, box and all, mid-sentence:
|
|
476
|
+
// the item being typed in stays on the page until the labeller leaves its box.
|
|
477
|
+
state.typingIn = typing ? typing.key.slice(0, typing.key.indexOf("|")) : null;
|
|
478
|
+
paint();
|
|
479
|
+
var again = typing && state.whyBoxes[typing.key];
|
|
480
|
+
if (again) { again.focus(); again.setSelectionRange(typing.start, typing.end); }
|
|
481
|
+
}
|
|
482
|
+
function paint() {
|
|
424
483
|
var off = !canWrite();
|
|
425
484
|
var done = DATA.items.filter(function (i) { return itemDone(i.id); }).length;
|
|
426
485
|
var total = DATA.items.length;
|
|
@@ -482,7 +541,8 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
482
541
|
progress.appendChild(note);
|
|
483
542
|
}
|
|
484
543
|
DATA.groups.forEach(function (g) {
|
|
485
|
-
|
|
544
|
+
var typingHere = groupItems(g.id).some(function (i) { return i.id === state.typingIn; });
|
|
545
|
+
if (onlyOpen && groupDone(g.id) && !typingHere) return;
|
|
486
546
|
app.appendChild(groupBlock(g, off, onlyOpen));
|
|
487
547
|
});
|
|
488
548
|
}
|
|
@@ -499,6 +559,10 @@ pre { overflow-x: auto; background: var(--surface); border: 1px solid var(--line
|
|
|
499
559
|
}
|
|
500
560
|
function broken() { state.store = "none"; render(); }
|
|
501
561
|
|
|
562
|
+
document.addEventListener("visibilitychange", function () {
|
|
563
|
+
if (document.visibilityState !== "visible") flushReasons();
|
|
564
|
+
});
|
|
565
|
+
window.addEventListener("pagehide", flushReasons);
|
|
502
566
|
render();
|
|
503
567
|
if (!window.claude || typeof window.claude.use !== "function") { broken(); return; }
|
|
504
568
|
window.claude.use("db").then(function (store) {
|