@bongos/core 1.19.909 → 1.19.911
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/.bongos-core.json +52 -32
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +62 -2
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +25 -25
- package/docs/copy-registry.json +29 -29
- package/docs/file-map.md +3 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/ideas.css +25 -0
- package/modules/hall-ui/public/ideas.html +12 -0
- package/modules/hall-ui/public/ideas.js +115 -2
- package/modules/ideas/landing-preview.js +180 -0
- package/modules/ideas/routes/inbox.js +47 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/api-alias-caller-scan.js +213 -0
- package/scripts/gds/fitness-ratchets.js +31 -0
- package/scripts/gds/fitness.js +1 -1
- package/scripts/gds/gen-file-map.js +96 -2
- package/scripts/gds/ship-check.js +1 -1
- package/scripts/hall-preview/server.js +14 -1
- package/src/module-api.js +1 -1
- package/tests/api_alias_caller_ratchet.mjs +130 -0
- package/tests/file_map.mjs +59 -0
- package/tests/idea_landing_preview.mjs +337 -0
- package/tests/idea_spark_hall.mjs +183 -0
|
@@ -60,6 +60,11 @@ const GOALS = [
|
|
|
60
60
|
function boot({
|
|
61
61
|
search = '', sparks = QUEUE, count = null, idea = SPARK, ideaOk = true, store = {},
|
|
62
62
|
versions = VERSIONS, goals = GOALS, suggestions = [],
|
|
63
|
+
// GRAVITY (task 1004083 / spec R02). The landing verdict the composer draws
|
|
64
|
+
// its one line from. `null` means the read FAILED — distinct from a verdict of
|
|
65
|
+
// `{ outcome: null }`, which is the server saying "nothing to state yet"; the
|
|
66
|
+
// line must go quiet for both, and there is a case below for each.
|
|
67
|
+
landing = undefined,
|
|
63
68
|
} = {}) {
|
|
64
69
|
const els = new Map();
|
|
65
70
|
const makeEl = () => ({
|
|
@@ -108,6 +113,21 @@ function boot({
|
|
|
108
113
|
return { status: 201, ok: true, data: { idea: { id: '900', grade: 'full', completeness_score: 80 } } };
|
|
109
114
|
},
|
|
110
115
|
postInboxIdDevelop: async (a) => { calls.push(['develop', a]); return { status: 200, ok: true, data: { idea: { ...idea, grade: 'full', completeness_score: 80, ideator_credit: 5 } } }; },
|
|
116
|
+
// task 1004083 (spec R02) — the landing preview. The DEFAULT mirrors the
|
|
117
|
+
// real matrix for the fixtures this file already files with: a goal named
|
|
118
|
+
// ⇒ it lands there, no goal ⇒ it floats. A test that cares about a
|
|
119
|
+
// particular verdict passes `landing` and overrides the lot.
|
|
120
|
+
postInboxPreviewLanding: async (a) => {
|
|
121
|
+
calls.push(['preview', a]);
|
|
122
|
+
if (landing === null) return { status: 500, ok: false, data: null };
|
|
123
|
+
if (landing !== undefined) return { status: 200, ok: true, data: landing };
|
|
124
|
+
const gid = a.body && a.body.goal_id;
|
|
125
|
+
if (gid != null) {
|
|
126
|
+
const g = goals.find((x) => String(x.id) === String(gid));
|
|
127
|
+
return { status: 200, ok: true, data: { outcome: 'routed', reason: 'bug_trusted_filer', status: 'ready', goal: { id: String(gid), title: g ? g.title : `goal ${gid}` }, version_id: 'BV1', refusal: null } };
|
|
128
|
+
}
|
|
129
|
+
return { status: 200, ok: true, data: { outcome: 'floats', reason: 'no_goal', status: null, goal: null, version_id: null, refusal: null } };
|
|
130
|
+
},
|
|
111
131
|
},
|
|
112
132
|
// task 1003069 (R08) — the quick-filing goal picker's reads: the building
|
|
113
133
|
// versions (to scope the suggester) and the open goal list (the picker's
|
|
@@ -515,5 +535,168 @@ await test('storage that refuses never takes the composer down, and says it is n
|
|
|
515
535
|
// the element. Without the markup pin this file would have certified a page
|
|
516
536
|
// that renders nothing.
|
|
517
537
|
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// GRAVITY — which goal will catch this thought, said before it is released
|
|
540
|
+
// (task 1004083 / spec R02)
|
|
541
|
+
//
|
|
542
|
+
// THE DEFECT THESE PIN is not a missing control: #fq-goal is a visible select.
|
|
543
|
+
// It is that the select's own labels are UNTRUE in two directions — "No goal
|
|
544
|
+
// (homeless)" for a bug (ADR 0262 routes it to the maintenance goal) and a named
|
|
545
|
+
// goal for a judgment kind (routing.js NEVER_ROUTE_KINDS). So the cases that
|
|
546
|
+
// matter most here are the two where the LINE AND THE PICKER DISAGREE.
|
|
547
|
+
//
|
|
548
|
+
// The same stub caveat the receipts block above records applies in full: the DOM
|
|
549
|
+
// fake manufactures an element for any id, so the markup pin is load-bearing and
|
|
550
|
+
// every other case below would stay green without it.
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
|
|
553
|
+
await test('ideas.html actually declares the gravity line (the stub would fake it otherwise)', async () => {
|
|
554
|
+
const html = fs.readFileSync(path.join(HALL, 'ideas.html'), 'utf8');
|
|
555
|
+
assert.match(html, /id="fq-gravity"/, 'the gravity line is in the real markup');
|
|
556
|
+
assert.match(html, /id="fq-gravity"[^>]*hidden/, 'and it starts hidden — nothing true to say yet');
|
|
557
|
+
assert.match(html, /id="fq-gravity"[^>]*aria-live="polite"/, 'and it is announced, not just painted');
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
await test('picking a goal says which goal will catch it, and that it is claimable', async () => {
|
|
561
|
+
const r = boot();
|
|
562
|
+
await settle(); await settle();
|
|
563
|
+
r.el('fq-title').value = 'the widget is broken';
|
|
564
|
+
r.el('fq-goal').value = '77';
|
|
565
|
+
r.el('fq-goal')._handlers.change();
|
|
566
|
+
await settle(); await settle();
|
|
567
|
+
assert.equal(r.el('fq-gravity').hidden, false, 'the line is shown');
|
|
568
|
+
assert.match(r.el('fq-gravity').textContent, /Goal Seventy Seven/, 'and it names the goal');
|
|
569
|
+
assert.match(r.el('fq-gravity').textContent, /anyone can claim/, 'and says the task is claimable');
|
|
570
|
+
assert.equal(r.el('fq-gravity').dataset.tone, 'lands');
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
await test('a goal that only lands at backlog says a nod is still needed', async () => {
|
|
574
|
+
const r = boot({ landing: { outcome: 'routed', reason: 'bug_awaiting_nod', status: 'backlog', goal: { id: '77', title: 'Goal Seventy Seven' }, version_id: 'BV1', refusal: null } });
|
|
575
|
+
await settle(); await settle();
|
|
576
|
+
r.el('fq-title').value = 'the widget is broken';
|
|
577
|
+
r.el('fq-title')._handlers.input();
|
|
578
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
579
|
+
await settle();
|
|
580
|
+
assert.match(r.el('fq-gravity').textContent, /waiting for a nod/, 'the two routed outcomes are not conflated');
|
|
581
|
+
assert.equal(r.el('fq-gravity').dataset.tone, 'lands', 'it still lands — the tone is about having a home, not about being claimable');
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
await test('a homeless BUG is told it lands anyway — the picker says "homeless" and that is false', async () => {
|
|
585
|
+
// ADR 0262: decideLanding routes a goal-less kind='bug' from the api vector
|
|
586
|
+
// into the version's maintenance goal. The select still reads "No goal
|
|
587
|
+
// (homeless)". This line is the correction, and it is the whole reason the
|
|
588
|
+
// verdict is read from the server instead of inferred from the select's value.
|
|
589
|
+
const r = boot({ landing: { outcome: 'routed_home', reason: 'bug_homeless_trusted_filer', status: 'ready', goal: null, version_id: 'BV1', refusal: null } });
|
|
590
|
+
await settle(); await settle();
|
|
591
|
+
r.el('fq-title').value = 'the widget is broken';
|
|
592
|
+
r.el('fq-title')._handlers.input();
|
|
593
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
594
|
+
await settle();
|
|
595
|
+
const said = r.el('fq-gravity').textContent;
|
|
596
|
+
assert.match(said, /A bug always lands/, 'it contradicts the picker rather than echoing it');
|
|
597
|
+
assert.match(said, /BV1/, 'and names whose maintenance goal catches it');
|
|
598
|
+
assert.equal(r.el('fq-gravity').dataset.tone, 'lands');
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
await test('a named goal that the matrix DECLINES says so instead of implying the goal caught it', async () => {
|
|
602
|
+
// The second lie: a criterion-proposal or a decision never routes, however
|
|
603
|
+
// confidently the select shows a goal beside it.
|
|
604
|
+
const r = boot({ landing: { outcome: 'floats', reason: 'judgment_kind', status: null, goal: { id: '77', title: 'Goal Seventy Seven' }, version_id: 'BV1', refusal: null } });
|
|
605
|
+
await settle(); await settle();
|
|
606
|
+
r.el('fq-title').value = 'should we split the hall';
|
|
607
|
+
r.el('fq-title')._handlers.input();
|
|
608
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
609
|
+
await settle();
|
|
610
|
+
const said = r.el('fq-gravity').textContent;
|
|
611
|
+
assert.match(said, /will not catch this/, 'the goal is named as NOT catching it');
|
|
612
|
+
assert.match(said, /inbox/, 'and the real destination is stated');
|
|
613
|
+
assert.equal(r.el('fq-gravity').dataset.tone, 'floats');
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
await test('letting it float is stated as an outcome, not left as an empty default', async () => {
|
|
617
|
+
const r = boot();
|
|
618
|
+
await settle(); await settle();
|
|
619
|
+
r.el('fq-title').value = 'a loose thought';
|
|
620
|
+
r.el('fq-title')._handlers.input();
|
|
621
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
622
|
+
await settle();
|
|
623
|
+
assert.equal(r.el('fq-gravity').hidden, false);
|
|
624
|
+
assert.match(r.el('fq-gravity').textContent, /floats in the inbox/);
|
|
625
|
+
assert.equal(r.el('fq-gravity').dataset.tone, 'floats', 'floating is neutral — it is a legal choice, not a warning');
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
await test('a refusal is shown in the filer\'s own words before they spend the filing', async () => {
|
|
629
|
+
const r = boot({ landing: { outcome: 'refused', reason: 'soft_landing', status: null, goal: { id: '77', title: 'Goal Seventy Seven' }, version_id: 'BV1', refusal: { code: 'protected_scope', reason: 'This goal reaches protected territory, so a filer below Metic cannot route work into it.' } } });
|
|
630
|
+
await settle(); await settle();
|
|
631
|
+
r.el('fq-title').value = 'touch the deploy core';
|
|
632
|
+
r.el('fq-title')._handlers.input();
|
|
633
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
634
|
+
await settle();
|
|
635
|
+
assert.match(r.el('fq-gravity').textContent, /protected territory/, 'the server\'s reason is passed through');
|
|
636
|
+
assert.equal(r.el('fq-gravity').dataset.tone, 'refused');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
await test('an empty title states nothing, and a failed read states nothing — never a guess', async () => {
|
|
640
|
+
const quiet = boot();
|
|
641
|
+
await settle(); await settle();
|
|
642
|
+
quiet.el('fq-title').value = ' ';
|
|
643
|
+
quiet.el('fq-title')._handlers.input();
|
|
644
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
645
|
+
await settle();
|
|
646
|
+
assert.equal(quiet.el('fq-gravity').hidden, true, 'nothing to file, nothing to say');
|
|
647
|
+
|
|
648
|
+
const down = boot({ landing: null });
|
|
649
|
+
await settle(); await settle();
|
|
650
|
+
down.el('fq-title').value = 'the widget is broken';
|
|
651
|
+
down.el('fq-title')._handlers.input();
|
|
652
|
+
await new Promise((res) => setTimeout(res, 320));
|
|
653
|
+
await settle();
|
|
654
|
+
assert.equal(down.el('fq-gravity').hidden, true, 'a preview outage degrades to silence, not to a wrong sentence');
|
|
655
|
+
assert.equal(down.el('fq-err').textContent, '', 'and never to an error under a half-typed thought');
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
await test('filing clears the line — gravity describes a draft, and the draft is gone', async () => {
|
|
659
|
+
const r = boot();
|
|
660
|
+
await settle(); await settle();
|
|
661
|
+
r.el('fq-title').value = 'the widget is broken';
|
|
662
|
+
r.el('fq-goal').value = '77';
|
|
663
|
+
r.el('fq-goal')._handlers.change();
|
|
664
|
+
await settle(); await settle();
|
|
665
|
+
// The TEXT is what proves it was shown, not `hidden`. The DOM stub starts an
|
|
666
|
+
// element at hidden:false, so asserting only on that passes when the line was
|
|
667
|
+
// never rendered at all — the same class of vacuous pass the markup pin above
|
|
668
|
+
// exists for, caught by reverting the goal-change redraw and watching this
|
|
669
|
+
// case stay green.
|
|
670
|
+
assert.match(r.el('fq-gravity').textContent, /Goal Seventy Seven/, 'shown while composing');
|
|
671
|
+
assert.equal(r.el('fq-gravity').hidden, false);
|
|
672
|
+
await r.submitQuick();
|
|
673
|
+
await settle();
|
|
674
|
+
assert.equal(r.el('fq-gravity').hidden, true, 'and gone once it is filed');
|
|
675
|
+
assert.equal(r.el('fq-gravity').textContent, '', 'and the sentence with it');
|
|
676
|
+
assert.equal(r.el('fq-receipts').hidden, false, 'the RECEIPT is what survives instead (task 1004082)');
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
// Every guard above was reverted and each redded its named case (RUN, and these
|
|
680
|
+
// are the measured counts, not the predicted ones):
|
|
681
|
+
// drop renderGravity from the goal-change handler -> 2 red (named-goal + filing-clears)
|
|
682
|
+
// infer from the select instead of the verdict -> 4 red (both lies, + backlog + refusal)
|
|
683
|
+
// drop the seq guard and paint every answer -> 0 red <-- see below
|
|
684
|
+
// rename the id in ideas.html -> 1 red (the markup pin ONLY)
|
|
685
|
+
//
|
|
686
|
+
// THE FIRST LINE COST A TEST FIX, and it is the reason to run these rather than
|
|
687
|
+
// reason about them. That probe first redded only ONE case: "filing clears the
|
|
688
|
+
// line" opened by asserting `hidden === false`, and the DOM stub starts every
|
|
689
|
+
// element at `hidden: false` — so the assertion passed while the line was never
|
|
690
|
+
// rendered at all. It asserts on the TEXT now. A stub's defaults can agree with
|
|
691
|
+
// a broken page, which is the same lesson the markup pin below teaches twice.
|
|
692
|
+
//
|
|
693
|
+
// THE THIRD LINE IS AN HONEST GAP, recorded rather than papered over. The
|
|
694
|
+
// out-of-order guard in refreshGravity is not covered: the fake resolves
|
|
695
|
+
// synchronously, so two previews cannot interleave here and a test that removed
|
|
696
|
+
// the guard would still pass. Pinning it needs a fake that delays per call,
|
|
697
|
+
// which is a different harness from this one. Filed as task 1004108 rather than
|
|
698
|
+
// claimed as tested — a green test over an unreachable branch would be a false
|
|
699
|
+
// claim of coverage, which is the failure this file's own note above warns about.
|
|
700
|
+
|
|
518
701
|
console.log(`\nidea_spark_hall: ${passed} passed, ${failed} failed`);
|
|
519
702
|
if (failed > 0) process.exit(1);
|