openvibers/openvibe.design Commits

Research first: keep a proposed rule in the register's areas (#286)

OP OpenVibers committed 2 days ago · 1c94a0225b · 5 files, +35 −5

A recommendation that would change a rule often has no area of its own;
the proposal now takes the cited rule's area, or products, so approving it
adds a rule the page can group. A fresh AI token is asked for on a 403
(the grant may have just changed), switching between start/attach clears
the other choice, and the component section counts the dives that actually
contributed a finding.

Changes

ddiff --git a/public/js/design.js b/public/js/design.js
iindex bce4b92..5e25fa6 100644
---- a/public/js/design.js
++++ b/public/js/design.js
@@@ -111,7 +111,19 @@
else if (input.name === 'models' || input.name === 'takes') fitModels();
else if (input.name === 'start_url') setText('start', startUrl() ? (() => { try { return new URL(startUrl()).hostname; } catch { return 'A page'; } })() : 'Blank');
else if (input.name === 'who') { all('input[name="models"]').forEach((x) => { x.checked = false; }); fitModels(); show(input); setTimeout(() => closeMods(null), 160); }
-- else if (input.name === 'research' || input.name === 'research_depth' || input.name === 'research_id' || input.name === 'research_ref') { fitResearch(); }
++ else if (input.name === 'research') {
++ if (input.checked) { const id = q('select[name="research_id"]'); if (id) id.value = ''; const ref = q('input[name="research_ref"]'); if (ref) ref.value = ''; }
++ fitResearch();
++ }
++ else if (input.name === 'research_id') {
++ if (input.value) { const tick = q('input[name="research"]'); if (tick) tick.checked = false; const ref = q('input[name="research_ref"]'); if (ref) ref.value = ''; }
++ fitResearch();
++ }
++ else if (input.name === 'research_ref') {
++ if (input.value.trim()) { const tick = q('input[name="research"]'); if (tick) tick.checked = false; const id = q('select[name="research_id"]'); if (id) id.value = ''; }
++ fitResearch();
++ }
++ else if (input.name === 'research_depth') fitResearch();
else if (input.closest('.dv-mod')) { show(input); setTimeout(() => closeMods(null), 160); }
save(); askCost();
});
@@@ -121,7 +133,10 @@
});
prompt.addEventListener('blur', askCost);
const researchRef = q('input[name="research_ref"]');
-- if (researchRef) researchRef.addEventListener('input', () => { fitResearch(); save(); askCost(); });
++ if (researchRef) researchRef.addEventListener('input', () => {
++ if (researchRef.value.trim()) { const tick = q('input[name="research"]'); if (tick) tick.checked = false; const id = q('select[name="research_id"]'); if (id) id.value = ''; }
++ fitResearch(); save(); askCost();
++ });
mods.forEach((m) => m.addEventListener('toggle', () => { if (m.open) closeMods(m); }));
document.addEventListener('click', (event) => { const open = mods.find((m) => m.open); if (open && (!open.contains(event.target) || event.target === open)) closeMods(null); });
document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { const open = mods.find((m) => m.open); if (open) { closeMods(null); open.querySelector('summary').focus(); } } });
ddiff --git a/server/deepdive/ai.js b/server/deepdive/ai.js
iindex 580e97a..7300366 100644
---- a/server/deepdive/ai.js
++++ b/server/deepdive/ai.js
@@@ -82,7 +82,8 @@ function createDeepDiveAi({ config, fetchImpl = globalThis.fetch, log = console
throw new DeepDiveError(502, 'deep_dive.unreachable', err && err.name === 'TimeoutError'
? 'OpenVibe.AI did not answer in time.' : 'OpenVibe.AI could not be reached.');
}
-- if (res.status === 401 && !token && !retried) {
++ if ((res.status === 401 || res.status === 403) && !token && !retried) {
++ // A grant that just changed (or a revoked token): ask Network for a fresh one once.
tokenClient.invalidate();
return call(method, path, body, { token, retried: true, baseUrl });
}
ddiff --git a/server/deepdive/report.js b/server/deepdive/report.js
iindex 6d5bbc8..6999fc2 100644
---- a/server/deepdive/report.js
++++ b/server/deepdive/report.js
@@@ -121,11 +121,22 @@ function proposedRulesOf(report) {
if (!(rule.status === 'would_change' || item.proposed_rule || item.proposes_rule)) continue;
const text = str(item.proposed_rule || item.rule_text || item.what || (typeof item === 'string' ? item : ''), 300);
if (!text) continue;
++ // Keep the row in one of the register's areas, or the page's grouping would drop it. The cited rule's
++ // own area is the best answer; a new rule lands in products, where most product rules live.
++ let area = str(item.area || item.group, 40);
++ if (!area) {
++ try {
++ const { RULES } = require('../rules/register');
++ const cited = RULES.find((x) => (rule.id && x.id === rule.id) || (rule.text && x.rule === rule.text));
++ if (cited && cited.area) area = cited.area;
++ } catch { /* the register is not readable here; the default below is fine */ }
++ }
++ if (!area) area = 'products';
out.push({
rule: text,
detail: str(rule.text || item.detail || item.description || item.what, 1200),
why: str(item.why || item.reason || item.evidence_note, 1200),
-- area: str(item.area || item.group, 40),
++ area: area.slice(0, 40),
evidence: evidenceOf(item),
ruleId: str(rule.id, 120) || null,
});
ddiff --git a/server/deepdive/service.js b/server/deepdive/service.js
iindex 9ea5a5d..b73b6c5 100644
---- a/server/deepdive/service.js
++++ b/server/deepdive/service.js
@@@ -222,8 +222,10 @@ function createDeepDiveService({ s, config, fetchImpl = globalThis.fetch, log =
if (!owner) return { component, count: 0, finds: [], reports: 0 };
const reports = await db.allDone({ limit: 60, owner });
const out = [];
++ const contributed = new Set();
for (const row of reports) {
const found = findsFor(row.report, component, limit);
++ if (found.length) contributed.add(row.id);
for (const find of found) {
const key = `${find.n || ''}|${find.title}|${find.crop || find.url || ''}`;
if (out.some((x) => x.key === key)) continue;
@@@ -231,7 +233,7 @@ function createDeepDiveService({ s, config, fetchImpl = globalThis.fetch, log =
reportUrl: row.report_url || null, depth: row.depth, readAt: row.updated_at });
}
}
-- return { component, count: out.length, finds: out.slice(0, limit), reports: reports.length };
++ return { component, count: out.length, finds: out.slice(0, limit), reports: contributed.size };
}
async function resume() {
ddiff --git a/test/deepdive.test.js b/test/deepdive.test.js
iindex 0820a89..40e1c35 100644
---- a/test/deepdive.test.js
++++ b/test/deepdive.test.js
@@@ -123,6 +123,7 @@ const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
assert.ok(!/<style|<html|class=/.test(n.brief), 'the brief is words, never a site\'s markup');
assert.equal(n.proposedRules.length, 2);
assert.match(n.proposedRules[0].rule, /pressed state|48 px/);
++ assert.ok(n.proposedRules.every((p) => p.area), 'a proposed rule keeps one of the register\'s areas');
assert.match(findsFor(report, 'button')[0].title, /button/i);
assert.equal(findsFor(report, 'carousel').length, 0);
});