[object Object]

← back to Rejected Prompts Viewer

UX: default borderline ON (show everything) + one-click 'Show all' reset button

6b96c63d4131863601f1835d8693f6ec3bffb544 · 2026-08-19 10:39:58 -0700 · Steve Abrams

Files touched

Diff

commit 6b96c63d4131863601f1835d8693f6ec3bffb544
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 19 10:39:58 2026 -0700

    UX: default borderline ON (show everything) + one-click 'Show all' reset button
---
 public/index.html | 12 ++++++++++--
 scan.py           | 39 +++++++++++++++++++++++++++++----------
 2 files changed, 39 insertions(+), 12 deletions(-)

diff --git a/public/index.html b/public/index.html
index f800356..f0c399c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -91,7 +91,8 @@
     <label id="densityWrap">Density
       <input type="range" id="density" min="1" max="5" step="1" value="3">
     </label>
-    <label class="toggle" id="blWrap"><input type="checkbox" id="borderline"><span>Show borderline candidates</span></label>
+    <label class="toggle" id="blWrap"><input type="checkbox" id="borderline" checked><span>Show borderline candidates</span></label>
+    <label>&nbsp;<button id="showall" class="seg" style="padding:7px 14px;font-weight:600;">✳︎ Show all</button></label>
     <label>Category
       <div class="chips" id="cats"></div>
     </label>
@@ -208,11 +209,18 @@ el('sort').onchange=()=>{ localStorage.setItem(LS('sort'),el('sort').value); ren
 el('q').oninput=render;
 el('borderline').onchange=()=>{ localStorage.setItem(LS('bl'),el('borderline').checked?'1':'0'); render(); };
 el('density').oninput=()=>{ document.documentElement.style.setProperty('--cols',el('density').value); localStorage.setItem(LS('density'),el('density').value); };
+el('showall').onclick=(e)=>{ e.preventDefault();
+  activeCats.clear();
+  el('cats').querySelectorAll('.chip').forEach(c=>c.classList.remove('on'));
+  el('q').value='';
+  el('borderline').checked=true; localStorage.setItem(LS('bl'),'1');
+  render();
+};
 
 function restore(){
   const s=localStorage.getItem(LS('sort')); if(s) el('sort').value=s;
   const d=localStorage.getItem(LS('density')); if(d){ el('density').value=d; document.documentElement.style.setProperty('--cols',d); }
-  if(localStorage.getItem(LS('bl'))==='1') el('borderline').checked=true;
+  if(localStorage.getItem(LS('bl'))==='0') el('borderline').checked=false; // default ON, honor explicit off
   const v=localStorage.getItem(LS('view')); if(v){ VIEW=v; el('viewseg').querySelectorAll('button').forEach(b=>b.classList.toggle('on', b.dataset.v===v)); }
   // URL params override (shareable view links): ?view=grid|list|table&bl=1
   const p=new URLSearchParams(location.search);
diff --git a/scan.py b/scan.py
index e4eb020..e63b79a 100644
--- a/scan.py
+++ b/scan.py
@@ -1,15 +1,23 @@
 #!/usr/bin/env python3
-"""Stage 1 — cheap recall net.
-Scan every Claude Code transcript for assistant text blocks that BOTH carry a
-decline verb AND a policy-reason keyword (copyright / trademark / IP / website-ToS),
-capturing the preceding user prompt. Writes /tmp/refusal_cands.json for the
-Stage-2 local-LLM precision pass (classify.py)."""
+"""Stage 1 — cheap recall net (two nets, unioned).
+Net A: assistant text with a decline verb AND a policy-reason keyword (high precision).
+Net B: assistant text with a STRONG refusal opener, regardless of keyword — catches
+       purely tone-based / ethics refusals that never say 'copyright' etc.
+The Stage-2 local-LLM pass (classify.py) is the arbiter of policy-refusal vs capability-limit.
+Writes /tmp/refusal_cands.json."""
 import json, glob, re, os
 
 PROJROOT = os.path.expanduser('~/.claude/projects')
+
 decline = re.compile(r"(I can'?t|I cannot|I won'?t|I'm not able|I am not able|I'm not going to|I'm not comfortable|I shouldn'?t|I have to decline|I'd rather not|not something I can|can'?t ethically|won'?t help|refus|decline to|not willing to|in good conscience)", re.I)
 reason = re.compile(r"(copyright|trademark|intellectual property|terms of service|\bToS\b|their terms|against .{0,20}terms|scrap\w* (without|permission|their)|circumvent|bypass\w* (the )?(bot|captcha|paywall|rate.?limit|detection|login|auth)|unauthorized access|impersonat|counterfeit|\bDMCA\b|someone else'?s (brand|work|design|content)|protected work|passing off|knockoff|knock-off|rip.?off .{0,15}brand|clone .{0,15}(brand|site|store))", re.I)
 
+# Net B — strong refusal openers that decline the REQUEST (not a capability limit).
+strong = re.compile(r"(I can'?t help (you )?with (that|this)|I can'?t assist with (that|this)|I won'?t help (you )?(with|do)|I'm not able to help with (that|this)|I have to decline|I'm going to decline|I'm not comfortable (doing|helping|with|creating|writing)|I can'?t in good conscience|I won'?t be able to help with|I can'?t (create|write|generate|provide|build|do) (that|this)|that'?s not something I('?ll| will| can) (help|do|assist)|I won'?t assist|against my (guidelines|values|principles)|I can'?t ethically|I'm not willing to|I need to decline|I can'?t participate in)", re.I)
+# de-noise Net B: drop obvious capability-limit phrasings
+capability = re.compile(r"(from (in )?here|from the shell|from this session|for you (here|myself)|on your end|only you can|you'?ll need to|requires? (you|a person|your)|quit\w* iTerm|kill\w* (this|the) session|log ?in (yourself|first)|enter your|your (identity|credentials|password|2fa|payout|tax))", re.I)
+
+seen = set()
 cands = []
 for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
     try:
@@ -38,10 +46,21 @@ for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
             for it in (m.get('content') or []):
                 if isinstance(it, dict) and it.get('type') == 'text':
                     tx = it.get('text', '')
-                    if len(tx) < 2500 and decline.search(tx) and reason.search(tx):
-                        cands.append({'file': os.path.basename(f), 'ts': d.get('timestamp'),
-                                      'user': (prev_user or '')[:600], 'refusal': tx[:800]})
-                        break
+                    if len(tx) >= 2500:
+                        continue
+                    netA = decline.search(tx) and reason.search(tx)
+                    netB = strong.search(tx) and not capability.search(tx)
+                    if not (netA or netB):
+                        continue
+                    key = (os.path.basename(f), tx[:200])
+                    if key in seen:
+                        continue
+                    seen.add(key)
+                    cands.append({'file': os.path.basename(f), 'ts': d.get('timestamp'),
+                                  'user': (prev_user or '')[:600], 'refusal': tx[:800],
+                                  'net': 'A' if netA else 'B'})
+                    break
 
 json.dump(cands, open('/tmp/refusal_cands.json', 'w'), indent=1)
-print(f"CANDIDATES: {len(cands)} -> /tmp/refusal_cands.json")
+from collections import Counter
+print(f"CANDIDATES: {len(cands)}  by-net={dict(Counter(c['net'] for c in cands))} -> /tmp/refusal_cands.json")

← f3d573a Widen to all 85 candidates + borderline toggle + table/list/  ·  back to Rejected Prompts Viewer  ·  Fix refusal-text corruption (verdict->confirmed key), widen b84882e →