diff --git a/app/api.py b/app/api.py
index 38ca71b..9fd8677 100644
--- a/app/api.py
+++ b/app/api.py
@@ -456,6 +456,7 @@ def create_app(db_path, img_dir, state_dir=None):
for r in data['items']]
data['items'] = items
data['mode'] = mode
+ data['prefs'] = {'rubric_debug': get_pref(conn, 'rubric_debug', 'off')}
data['can_forge'] = (mode != 'strict'
or secmod.fresh_profile_exists(conn, sec))
data['counts'] = secmod.section_counts(conn, sec)
@@ -653,7 +654,8 @@ def create_app(db_path, img_dir, state_dir=None):
def set_pref_endpoint(body: dict):
key = body.get('key')
value = body.get('value')
- allowed = {'forge_mode': ('ondemand', 'strict')}
+ allowed = {'forge_mode': ('ondemand', 'strict'),
+ 'rubric_debug': ('off', 'on')}
if key not in allowed or value not in allowed[key]:
raise HTTPException(status_code=400,
detail='unknown or invalid pref')
diff --git a/app/static/index.html b/app/static/index.html
index 64149ac..2fbd725 100644
--- a/app/static/index.html
+++ b/app/static/index.html
@@ -321,6 +321,7 @@ nav{height:calc(68px + env(safe-area-inset-bottom,0px));display:flex;align-items
Forge:on demand⇄strict
+ Why breakdown:off⇄on
@@ -805,7 +806,9 @@ function openDetail(data,ctx){
$('detail').classList.add('on'); $('dback').classList.add('on');
$('dbody').scrollTop=0;
}
-/* P3 dossier rank block: gauge + points + strongest ± lines + audit footer */
+/* P3 dossier rank block: gauge + points% minimal; the full "why" breakdown
+ and the audit footer render only when prefs.rubric_debug === 'on' (§10.5). */
+function rubricOn(){ return !!(section&§ion.prefs&§ion.prefs.rubric_debug==='on'); }
function fillRank(data){
if(!data||data.tank==null||data.tank.value==null){
$('drank').style.display='none';
@@ -813,21 +816,27 @@ function fillRank(data){
}
$('drank').style.display='flex';
$('drankTank').innerHTML=tankSVG(data.tank,true);
- $('dpts').textContent=data.tank.value+'/100';
- const lines=(data.points_breakdown||[]).slice()
- .sort((a,b)=>Math.abs(b.value)-Math.abs(a.value)).slice(0,4);
- $('dlines').innerHTML=lines.map(l=>{
- const lbl= l.source==='agent'?('agent: '+l.label):l.label;
- const n= l.value>=0?(' +'+l.value):(' '+l.value);
- return `· ${lbl}${n}
`;
- }).join('');
- const ri= section&§ion.read_info;
- $('dfoot').textContent= ri?`from ${ri.events} strikes · read ${relTime(ri.profile_at)}`:'';
- // list/matches payloads carry no breakdown — fill it on the quiet
- if(!data.points_breakdown&&data.id&&!legacyMode){
- getJSON('/api/listing/'+data.id).then(full=>{
- if(full&&full.points_breakdown&&dCtx) fillRank(full);
- });
+ $('dpts').textContent=(data.tank.value|0)+'%';
+ if(rubricOn()){
+ const lines=(data.points_breakdown||[]).slice()
+ .sort((a,b)=>Math.abs(b.value)-Math.abs(a.value)).slice(0,4);
+ $('dlines').innerHTML=lines.map(l=>{
+ const lbl= l.source==='agent'?('agent: '+l.label):l.label;
+ const n= l.value>=0?(' +'+l.value):(' '+l.value);
+ return `· ${lbl}${n}
`;
+ }).join('');
+ const ri= section&§ion.read_info;
+ $('dfoot').textContent= ri?`from ${ri.events} strikes · read ${relTime(ri.profile_at)}`:'';
+ // list/matches payloads carry no breakdown — fill it on the quiet
+ if(!data.points_breakdown&&data.id&&!legacyMode){
+ getJSON('/api/listing/'+data.id).then(full=>{
+ if(full&&full.points_breakdown&&dCtx) fillRank(full);
+ });
+ }
+ }else{
+ // minimal display: gauge + % only; skip the quiet enrichment fetch
+ $('dlines').innerHTML='';
+ $('dfoot').textContent='';
}
}
function closeDetail(){ const d=$('detail'); d.style.transition=''; d.style.transform=''; d.classList.remove('on'); $('dback').classList.remove('on'); dCtx=null; dEntry=null; }
@@ -1190,6 +1199,7 @@ function updateSectionUI(body){
sectDecided=decided;
paintSect();
paintMode();
+ paintRubricMode();
const c=body.counts||{};
$('repCounts').innerHTML=`♥ ${c.likes||0} · ★ ${c.maybes||0} · ✕ ${c.nopes||0}`;
const hits=[];
@@ -1203,6 +1213,21 @@ async function setMode(m){
}
$('modeOnd').onclick=()=>setMode('ondemand');
$('modeStr').onclick=()=>setMode('strict');
+function paintRubricMode(){
+ const on=rubricOn();
+ $('tRubOff').classList.toggle('on',!on);
+ $('tRubOn').classList.toggle('on',on);
+}
+async function setRubricDebug(m){
+ const res=await fetch('/api/prefs',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({key:'rubric_debug',value:m})}).catch(()=>null);
+ if(res&&res.ok){
+ if(section){ section.prefs=section.prefs||{}; section.prefs.rubric_debug=m; }
+ paintRubricMode();
+ if(!legacyMode) loadSection(); // repaint any dossier already open
+ }
+}
+$('tRubOff').onclick=()=>setRubricDebug('off');
+$('tRubOn').onclick=()=>setRubricDebug('on');
$('btnForge').onclick=async()=>{
if(forgeBusy||!section) return;
forgeBusy=true;