← 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
M public/index.htmlM scan.py
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> <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 →