Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files
app.py
CHANGED
|
@@ -829,9 +829,54 @@ body.wg-light-mode footer { display: none !important; }
|
|
| 829 |
.wg-persona-label {
|
| 830 |
font-style: italic; font-family: 'EB Garamond', serif;
|
| 831 |
font-size: 0.78rem; color: var(--wg-yellow); letter-spacing: 0;
|
| 832 |
-
font-weight: 400;
|
|
|
|
| 833 |
}
|
| 834 |
#wg-practice .wg-persona-label { color: #b45309 !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 835 |
|
| 836 |
.wg-another-take {
|
| 837 |
float: right; cursor: pointer; font-family: 'EB Garamond', serif;
|
|
@@ -960,6 +1005,13 @@ body.wg-light-mode footer { display: none !important; }
|
|
| 960 |
background: #f5f0e6; border-left: 3px solid #2d6a4f;
|
| 961 |
padding: 0.75rem 1rem; border-radius: 0 10px 10px 0;
|
| 962 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 963 |
|
| 964 |
/* ββ Arcade Character Selector βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 965 |
.wg-arcade {
|
|
@@ -1442,17 +1494,38 @@ window.wgOpenLatestTrace = function() {
|
|
| 1442 |
}
|
| 1443 |
try {
|
| 1444 |
var payload = JSON.parse(raw);
|
|
|
|
| 1445 |
var logs = Array.isArray(payload.logs) ? payload.logs : [];
|
| 1446 |
var summary = [
|
| 1447 |
['route', payload.route || 'quick_wit'],
|
| 1448 |
['winner', payload.winning_persona || 'n/a'],
|
| 1449 |
-
['twist',
|
| 1450 |
['scenes', String((payload.retrieved_scenes || []).length)],
|
| 1451 |
['candidates', String((payload.candidates || []).length)]
|
| 1452 |
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1453 |
var summaryHtml = summary.map(function(item) {
|
| 1454 |
return '<span class="wg-trace-pill"><strong>' + item[0] + '</strong> ' + item[1] + '</span>';
|
| 1455 |
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1456 |
var logHtml = logs.length ? logs.map(function(line) {
|
| 1457 |
return '<div class="wg-trace-logline">' +
|
| 1458 |
'<span class="wg-trace-step">' + (line.step || '') + '</span>' +
|
|
@@ -1466,9 +1539,10 @@ window.wgOpenLatestTrace = function() {
|
|
| 1466 |
.replace(/>/g, '>');
|
| 1467 |
b.innerHTML =
|
| 1468 |
'<div class="wg-trace-modal-head">' +
|
| 1469 |
-
'<div><div class="wg-trace-modal-title">
|
| 1470 |
'</div>' +
|
| 1471 |
'<div class="wg-trace-summary">' + summaryHtml + '</div>' +
|
|
|
|
| 1472 |
'<div class="wg-trace-logbox"><div class="wg-trace-logtitle">EXECUTION LOG</div>' + logHtml + '</div>' +
|
| 1473 |
'<div class="wg-trace-jsonbox"><div class="wg-trace-jsonhead">TRACE JSON</div><pre class="wg-trace-jsonpre">' + jsonPretty + '</pre></div>';
|
| 1474 |
o.style.display = 'flex';
|
|
@@ -1520,6 +1594,10 @@ window.wgOpenScene = function(character, show, setup, response, why, avatarUrl,
|
|
| 1520 |
+ '<div class="wg-pop-bubble">' + response + '</div>'
|
| 1521 |
+ '</div></div>'
|
| 1522 |
+ '<div class="wg-pop-why">'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1523 |
+ '<div class="wg-pop-why-title">WHY IT WORKS</div>'
|
| 1524 |
+ '<div class="wg-pop-why-body">' + why + '</div>'
|
| 1525 |
+ '</div>';
|
|
@@ -2417,7 +2495,7 @@ def build_ui():
|
|
| 2417 |
'<path d="M16.5 8.5l1.4-1.4 2.5 2.5-1.4 1.4M15.7 9.3l2.5 2.5M15.2 14.8l3.6-3.6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>'
|
| 2418 |
'</svg>'
|
| 2419 |
'</span>'
|
| 2420 |
-
'<span class="wg-trace-launch-text">
|
| 2421 |
'</button>'
|
| 2422 |
'</div>'
|
| 2423 |
)
|
|
|
|
| 829 |
.wg-persona-label {
|
| 830 |
font-style: italic; font-family: 'EB Garamond', serif;
|
| 831 |
font-size: 0.78rem; color: var(--wg-yellow); letter-spacing: 0;
|
| 832 |
+
font-weight: 400; background: transparent; border: none;
|
| 833 |
+
padding: 0; cursor: pointer;
|
| 834 |
}
|
| 835 |
#wg-practice .wg-persona-label { color: #b45309 !important; }
|
| 836 |
+
.wg-insight-strip {
|
| 837 |
+
margin-top: 0.85rem;
|
| 838 |
+
padding: 0.8rem 0.95rem;
|
| 839 |
+
border-radius: 14px;
|
| 840 |
+
background: linear-gradient(180deg, rgba(255,250,241,0.98), rgba(245,240,232,0.98));
|
| 841 |
+
border: 1px solid rgba(180,83,9,0.16);
|
| 842 |
+
}
|
| 843 |
+
.wg-insight-title {
|
| 844 |
+
font-family: 'Bebas Neue', sans-serif;
|
| 845 |
+
font-size: 0.9rem;
|
| 846 |
+
letter-spacing: 0.12em;
|
| 847 |
+
color: #92400e !important;
|
| 848 |
+
}
|
| 849 |
+
.wg-insight-sub {
|
| 850 |
+
margin-top: 0.2rem;
|
| 851 |
+
font-size: 0.88rem;
|
| 852 |
+
color: #6b6258 !important;
|
| 853 |
+
}
|
| 854 |
+
.wg-insight-buttons {
|
| 855 |
+
display: flex;
|
| 856 |
+
flex-wrap: wrap;
|
| 857 |
+
gap: 0.45rem;
|
| 858 |
+
margin-top: 0.65rem;
|
| 859 |
+
}
|
| 860 |
+
.wg-insight-btn {
|
| 861 |
+
-webkit-appearance: none !important;
|
| 862 |
+
appearance: none !important;
|
| 863 |
+
background: #fffaf1 !important;
|
| 864 |
+
border: 1px solid rgba(180,83,9,0.22) !important;
|
| 865 |
+
color: #9a3412 !important;
|
| 866 |
+
border-radius: 999px !important;
|
| 867 |
+
padding: 0.42rem 0.72rem !important;
|
| 868 |
+
font-family: 'Bebas Neue', sans-serif;
|
| 869 |
+
font-size: 0.8rem;
|
| 870 |
+
letter-spacing: 0.09em;
|
| 871 |
+
cursor: pointer;
|
| 872 |
+
line-height: 1;
|
| 873 |
+
box-shadow: none !important;
|
| 874 |
+
}
|
| 875 |
+
.wg-insight-btn:hover {
|
| 876 |
+
background: #ffffff !important;
|
| 877 |
+
border-color: rgba(180,83,9,0.42) !important;
|
| 878 |
+
transform: translateY(-1px);
|
| 879 |
+
}
|
| 880 |
|
| 881 |
.wg-another-take {
|
| 882 |
float: right; cursor: pointer; font-family: 'EB Garamond', serif;
|
|
|
|
| 1005 |
background: #f5f0e6; border-left: 3px solid #2d6a4f;
|
| 1006 |
padding: 0.75rem 1rem; border-radius: 0 10px 10px 0;
|
| 1007 |
}
|
| 1008 |
+
.wg-pop-minihead {
|
| 1009 |
+
font-family: 'Bebas Neue', sans-serif;
|
| 1010 |
+
font-size: 0.8rem;
|
| 1011 |
+
letter-spacing: 0.12em;
|
| 1012 |
+
color: #2563eb !important;
|
| 1013 |
+
margin: 0.9rem 0 0.35rem;
|
| 1014 |
+
}
|
| 1015 |
|
| 1016 |
/* ββ Arcade Character Selector βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 1017 |
.wg-arcade {
|
|
|
|
| 1494 |
}
|
| 1495 |
try {
|
| 1496 |
var payload = JSON.parse(raw);
|
| 1497 |
+
var meta = payload.metadata || {};
|
| 1498 |
var logs = Array.isArray(payload.logs) ? payload.logs : [];
|
| 1499 |
var summary = [
|
| 1500 |
['route', payload.route || 'quick_wit'],
|
| 1501 |
['winner', payload.winning_persona || 'n/a'],
|
| 1502 |
+
['twist', meta.twist_potential != null ? String(meta.twist_potential) : 'n/a'],
|
| 1503 |
['scenes', String((payload.retrieved_scenes || []).length)],
|
| 1504 |
['candidates', String((payload.candidates || []).length)]
|
| 1505 |
];
|
| 1506 |
+
function fmt(value) {
|
| 1507 |
+
return String(value || '').replace(/_/g, ' ');
|
| 1508 |
+
}
|
| 1509 |
+
function title(value) {
|
| 1510 |
+
return fmt(value).toUpperCase();
|
| 1511 |
+
}
|
| 1512 |
var summaryHtml = summary.map(function(item) {
|
| 1513 |
return '<span class="wg-trace-pill"><strong>' + item[0] + '</strong> ' + item[1] + '</span>';
|
| 1514 |
}).join('');
|
| 1515 |
+
var conceptHtml = '<div class="wg-pop-show">HOW WITGYM READ THIS MOMENT</div>'
|
| 1516 |
+
+ '<div class="wg-pop-bio" style="font-style:normal">'
|
| 1517 |
+
+ 'This is the teaching layer behind the joke: what kind of situation this is, what pressure drives it, which precedent scenes matched it, and which comic lens shaped the line.'
|
| 1518 |
+
+ '</div>'
|
| 1519 |
+
+ '<div class="wg-pop-minihead">SITUATION PATTERN</div>'
|
| 1520 |
+
+ '<div class="wg-pop-bio" style="font-style:normal">' + title(meta.archetype || 'unknown') + '</div>'
|
| 1521 |
+
+ '<div class="wg-pop-minihead">SOCIAL PRESSURE</div>'
|
| 1522 |
+
+ '<div class="wg-pop-bio" style="font-style:normal">' + title(meta.tension_type || 'unknown') + '</div>'
|
| 1523 |
+
+ '<div class="wg-pop-minihead">COMEDY COMPLEXITY</div>'
|
| 1524 |
+
+ '<div class="wg-pop-bio" style="font-style:normal">' + (meta.twist_potential != null ? String(meta.twist_potential) + '/10' : 'n/a') + ' Β· Higher means more moving parts to play with.</div>'
|
| 1525 |
+
+ '<div class="wg-pop-minihead">PERSONA LENS</div>'
|
| 1526 |
+
+ '<div class="wg-pop-bio" style="font-style:normal">' + title(payload.winning_persona || 'n/a') + ' Β· The comic angle used for the final line.</div>'
|
| 1527 |
+
+ '<div class="wg-pop-minihead">RETRIEVED CONTEXT</div>'
|
| 1528 |
+
+ '<div class="wg-pop-bio" style="font-style:normal">' + String((payload.retrieved_scenes || []).length) + ' Office scene(s) matched this pattern. They are reference cases, not lines being copied.</div>';
|
| 1529 |
var logHtml = logs.length ? logs.map(function(line) {
|
| 1530 |
return '<div class="wg-trace-logline">' +
|
| 1531 |
'<span class="wg-trace-step">' + (line.step || '') + '</span>' +
|
|
|
|
| 1539 |
.replace(/>/g, '>');
|
| 1540 |
b.innerHTML =
|
| 1541 |
'<div class="wg-trace-modal-head">' +
|
| 1542 |
+
'<div><div class="wg-trace-modal-title">HOW WITGYM BUILT THIS</div><div class="wg-trace-modal-sub">Student-friendly notes first, raw trace second.</div></div>' +
|
| 1543 |
'</div>' +
|
| 1544 |
'<div class="wg-trace-summary">' + summaryHtml + '</div>' +
|
| 1545 |
+
conceptHtml +
|
| 1546 |
'<div class="wg-trace-logbox"><div class="wg-trace-logtitle">EXECUTION LOG</div>' + logHtml + '</div>' +
|
| 1547 |
'<div class="wg-trace-jsonbox"><div class="wg-trace-jsonhead">TRACE JSON</div><pre class="wg-trace-jsonpre">' + jsonPretty + '</pre></div>';
|
| 1548 |
o.style.display = 'flex';
|
|
|
|
| 1594 |
+ '<div class="wg-pop-bubble">' + response + '</div>'
|
| 1595 |
+ '</div></div>'
|
| 1596 |
+ '<div class="wg-pop-why">'
|
| 1597 |
+
+ '<div class="wg-pop-why-title">WHY THIS SCENE WAS RETRIEVED</div>'
|
| 1598 |
+
+ '<div class="wg-pop-why-body">This is a reference case with a similar comedy pattern, so the coach can borrow the mechanism without copying the line.</div>'
|
| 1599 |
+
+ '</div>'
|
| 1600 |
+
+ '<div class="wg-pop-why">'
|
| 1601 |
+ '<div class="wg-pop-why-title">WHY IT WORKS</div>'
|
| 1602 |
+ '<div class="wg-pop-why-body">' + why + '</div>'
|
| 1603 |
+ '</div>';
|
|
|
|
| 2495 |
'<path d="M16.5 8.5l1.4-1.4 2.5 2.5-1.4 1.4M15.7 9.3l2.5 2.5M15.2 14.8l3.6-3.6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>'
|
| 2496 |
'</svg>'
|
| 2497 |
'</span>'
|
| 2498 |
+
'<span class="wg-trace-launch-text">Comedy Notes</span>'
|
| 2499 |
'</button>'
|
| 2500 |
'</div>'
|
| 2501 |
)
|
notes/agent-memory/cases/2026-06-15-ui-browser-testing-normal-branch.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# UI browser testing in this repo should move from worktree to normal branch
|
| 2 |
+
|
| 3 |
+
## Problem
|
| 4 |
+
- UI closed-loop testing with the in-app browser became unreliable while working from a Codex git worktree.
|
| 5 |
+
- The agent needed browser automation on localhost or browser-visible artifacts, but branch/worktree metadata and local runtime setup kept creating friction.
|
| 6 |
+
|
| 7 |
+
## Why It Persisted
|
| 8 |
+
- The code changes themselves were frontend-only and small, but verification depended on multiple layers: git worktree state, local runtime availability, and browser URL-policy constraints.
|
| 9 |
+
- Detached HEAD / worktree-specific git metadata created extra branch-management friction right before UI verification.
|
| 10 |
+
- The browser surface blocked direct file:// navigation, which made static preview verification harder unless served over localhost.
|
| 11 |
+
|
| 12 |
+
## Paths Tried
|
| 13 |
+
- Tried to continue from detached HEAD in the worktree, then corrected to a feature branch off origin/main.
|
| 14 |
+
- Tried to launch the full app with uv; hit cache permission issues and then a uv runtime panic unrelated to product code.
|
| 15 |
+
- Tried to open a static preview via file:// in the browser; browser security policy blocked that path.
|
| 16 |
+
- Switched to the safer localhost-preview approach.
|
| 17 |
+
|
| 18 |
+
## Root Cause
|
| 19 |
+
- In this repo, UI verification from a Codex worktree can fail for operational reasons that are orthogonal to the UI change itself: shared git worktree metadata permissions, missing local dependency installation in the worktree, and browser policy restrictions on file:// URLs.
|
| 20 |
+
|
| 21 |
+
## Pareto-Optimal Solution
|
| 22 |
+
- If UI testing starts fighting the agent while on a worktree, switch to a normal branch checkout for browser-based validation rather than burning time debugging worktree-specific friction.
|
| 23 |
+
- For static UI proof, prefer serving a preview over localhost instead of using file://, because the browser surface allows localhost but may block direct local-file navigation.
|
| 24 |
+
|
| 25 |
+
## Evidence
|
| 26 |
+
- `git checkout -b ...` from the worktree required elevated access to shared worktree git metadata outside the sandbox.
|
| 27 |
+
- `uv run python app.py` failed first on cache permissions, then with a uv/system-configuration panic.
|
| 28 |
+
- `python3 app.py` failed because the worktree runtime lacked installed dependencies like `python-dotenv`.
|
| 29 |
+
- Browser automation rejected `file:///private/tmp/...` with an explicit URL-policy block.
|
| 30 |
+
- `python3 -m http.server ...` on localhost succeeded once started with elevated permission.
|
| 31 |
+
|
| 32 |
+
## Future-Agent Rules
|
| 33 |
+
- If you are on a Codex worktree and UI testing starts failing for operational reasons, switch to a normal branch before spending more time on the worktree setup.
|
| 34 |
+
- For browser automation in this repo, prefer localhost-served previews over file:// artifacts.
|
| 35 |
+
- Distinguish environment/runtime failures from product regressions; do not misdiagnose launcher or policy problems as UI code bugs.
|
| 36 |
+
|
| 37 |
+
## Follow-Ups
|
| 38 |
+
- A normal-branch verification path is preferred for future high-pressure UI iterations.
|
| 39 |
+
- If full app runtime is required, ensure the branch environment has the expected dependencies installed before starting the browser loop.
|
notes/agent-memory/durable_memory.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## 2026-06-15 - UI Browser Testing On Worktrees
|
| 2 |
+
- Problem: browser-based UI validation in this repo became fragile from a Codex git worktree even when the UI change itself was small.
|
| 3 |
+
- Root cause: worktree git metadata, missing runtime dependencies in the worktree, and browser blocking of `file://` previews created verification friction unrelated to the code.
|
| 4 |
+
- Keep doing: if browser testing gets sticky, switch to a normal branch checkout and serve previews over localhost.
|
| 5 |
+
- Avoid: spending hackathon time debugging worktree-specific UI testing failures or relying on `file://` preview URLs.
|
witgym/debug_render.py
CHANGED
|
@@ -52,22 +52,29 @@ def _fmt_chip(value: str) -> str:
|
|
| 52 |
# Succinct definitions for each comedy structural property β shown on chip click
|
| 53 |
_CHIP_DEFS: dict[str, tuple[str, str]] = {
|
| 54 |
# Archetypes
|
| 55 |
-
"status_assertion": ("STATUS ASSERTION",
|
| 56 |
-
"self_delusion": ("SELF-DELUSION",
|
| 57 |
-
"power_inversion": ("POWER INVERSION",
|
| 58 |
-
"anxiety_escalation": ("ANXIETY ESCALATION",
|
| 59 |
-
"social_fail": ("SOCIAL PERFORMANCE FAIL", "
|
| 60 |
-
"misplaced_conf": ("MISPLACED CONFIDENCE",
|
| 61 |
# Tensions
|
| 62 |
-
"social_embarrass": ("SOCIAL EMBARRASSMENT",
|
| 63 |
-
"existential": ("EXISTENTIAL ANXIETY",
|
| 64 |
-
"status_threat": ("STATUS THREAT",
|
| 65 |
-
"identity_expose": ("IDENTITY EXPOSURE",
|
| 66 |
-
"logic_collapse": ("LOGIC COLLAPSE",
|
| 67 |
# Distances
|
| 68 |
-
"mild": ("MILD VIOLATION",
|
| 69 |
-
"moderate": ("MODERATE VIOLATION",
|
| 70 |
-
"sharp": ("SHARP VIOLATION",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
|
|
@@ -260,6 +267,46 @@ def _chip_onclick(value: str) -> str:
|
|
| 260 |
return f"wgOpenChip({_jstr(title)},{_jstr(defn)})"
|
| 261 |
|
| 262 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
def _meta_pass1_html(meta) -> str:
|
| 264 |
arc_click = _chip_onclick(meta.archetype.value)
|
| 265 |
ten_click = _chip_onclick(meta.tension_type.value)
|
|
@@ -534,7 +581,10 @@ def format_trace_html(result: WitGymResponse, user_input: str, show_debug: bool
|
|
| 534 |
|
| 535 |
# Winning persona label β use pre-compression value stored in response
|
| 536 |
winning_persona = result.winning_persona
|
| 537 |
-
persona_label =
|
|
|
|
|
|
|
|
|
|
| 538 |
|
| 539 |
# Another take button (only if alternatives exist)
|
| 540 |
another_take_btn = (
|
|
@@ -571,6 +621,7 @@ def format_trace_html(result: WitGymResponse, user_input: str, show_debug: bool
|
|
| 571 |
]
|
| 572 |
if result.explanation:
|
| 573 |
parts.append(_explanation_panel_html(result.explanation))
|
|
|
|
| 574 |
|
| 575 |
# ββ Drill chips (coaching follow-ups) ββββββββββββββββββββββββββββββββββ
|
| 576 |
if is_last:
|
|
|
|
| 52 |
# Succinct definitions for each comedy structural property β shown on chip click
|
| 53 |
_CHIP_DEFS: dict[str, tuple[str, str]] = {
|
| 54 |
# Archetypes
|
| 55 |
+
"status_assertion": ("ARCHETYPE Β· STATUS ASSERTION", "What it is: someone is trying to claim authority, status, or competence. Why it matters: the joke works by exposing the gap between the claim and the reality."),
|
| 56 |
+
"self_delusion": ("ARCHETYPE Β· SELF-DELUSION", "What it is: someone believes a flattering story about themselves that reality cannot support. Why it matters: comedy lands when the fantasy finally runs into the facts."),
|
| 57 |
+
"power_inversion": ("ARCHETYPE Β· POWER INVERSION", "What it is: the expected pecking order flips. Why it matters: surprise grows when the person who should control the room suddenly loses it."),
|
| 58 |
+
"anxiety_escalation": ("ARCHETYPE Β· ANXIETY ESCALATION", "What it is: a small worry spirals into a full disaster movie. Why it matters: the joke gets sharper as the reaction becomes bigger than the actual problem."),
|
| 59 |
+
"social_fail": ("ARCHETYPE Β· SOCIAL PERFORMANCE FAIL", "What it is: someone tries to look normal, smooth, or impressive and fumbles it. Why it matters: comedy comes from watching the performance collapse in public."),
|
| 60 |
+
"misplaced_conf": ("ARCHETYPE Β· MISPLACED CONFIDENCE", "What it is: someone sounds certain without having the facts. Why it matters: the joke exposes confidence that has outrun competence."),
|
| 61 |
# Tensions
|
| 62 |
+
"social_embarrass": ("TENSION Β· SOCIAL EMBARRASSMENT", "What it is: the fear of looking foolish in front of other people. Why it matters: embarrassment raises the emotional stakes, so even a short line can hit hard."),
|
| 63 |
+
"existential": ("TENSION Β· EXISTENTIAL ANXIETY", "What it is: the moment quietly touches identity, meaning, or dread. Why it matters: deeper fear gives the joke weight instead of making it feel throwaway."),
|
| 64 |
+
"status_threat": ("TENSION Β· STATUS THREAT", "What it is: someone's rank, credibility, or belonging feels under pressure. Why it matters: status danger creates friction, and friction gives the line bite."),
|
| 65 |
+
"identity_expose": ("TENSION Β· IDENTITY EXPOSURE", "What it is: the person's mask is slipping. Why it matters: jokes get stronger when they reveal the truth the speaker was trying to hide."),
|
| 66 |
+
"logic_collapse": ("TENSION Β· LOGIC COLLAPSE", "What it is: the person's explanation breaks under its own rules. Why it matters: once the logic collapses, the line can simply point at the wreckage."),
|
| 67 |
# Distances
|
| 68 |
+
"mild": ("SHARPNESS Β· MILD VIOLATION", "What it is: a safe, lightly subversive joke. Why it matters: this keeps the line playful instead of confrontational."),
|
| 69 |
+
"moderate": ("SHARPNESS Β· MODERATE VIOLATION", "What it is: a joke with some edge, but still recoverable. Why it matters: this is often the sweet spot for sounding bold without losing the room."),
|
| 70 |
+
"sharp": ("SHARPNESS Β· SHARP VIOLATION", "What it is: a line that cuts close to the bone. Why it matters: sharper jokes can win bigger laughs, but they also raise the risk of silence."),
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
_PERSONA_DEFS: dict[str, tuple[str, str]] = {
|
| 74 |
+
"cynic": ("PERSONA Β· CYNIC", "This lens says the quiet part out loud. It spots the real motive, the hidden cost, or the ugly truth underneath the situation."),
|
| 75 |
+
"conviction": ("PERSONA Β· CONVICTION", "This lens commits completely to a wrong belief. It is funny because the certainty exposes something true about the speaker."),
|
| 76 |
+
"absurdist": ("PERSONA Β· ABSURDIST", "This lens follows the situation's logic farther than a normal person would. It makes the joke by treating the spiral as perfectly reasonable."),
|
| 77 |
+
"bisociate": ("PERSONA Β· BISOCIATE", "This lens jumps to a different but structurally matching world. It works when the same comic pattern suddenly shows up somewhere unexpected."),
|
| 78 |
}
|
| 79 |
|
| 80 |
|
|
|
|
| 267 |
return f"wgOpenChip({_jstr(title)},{_jstr(defn)})"
|
| 268 |
|
| 269 |
|
| 270 |
+
def _persona_onclick(value: str) -> str:
|
| 271 |
+
title, defn = _PERSONA_DEFS.get(value, (f"PERSONA Β· {_fmt_chip(value)}", "This is the comic lens the coach used to write the line."))
|
| 272 |
+
return f"wgOpenChip({_jstr(title)},{_jstr(defn)})"
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def _insight_button(label: str, onclick: str) -> str:
|
| 276 |
+
return (
|
| 277 |
+
f'<button class="wg-insight-btn" type="button" onclick="{_esc(onclick)}">'
|
| 278 |
+
f'{_esc(label)}'
|
| 279 |
+
'</button>'
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _coach_notes_html(result: WitGymResponse) -> str:
|
| 284 |
+
meta = result.metadata
|
| 285 |
+
buttons = [
|
| 286 |
+
_insight_button("situation pattern", _chip_onclick(meta.archetype.value)),
|
| 287 |
+
_insight_button("social pressure", _chip_onclick(meta.tension_type.value)),
|
| 288 |
+
_insight_button(
|
| 289 |
+
f"complexity {meta.twist_potential}/10",
|
| 290 |
+
f"wgOpenChip({_jstr('COMEDY COMPLEXITY')},{_jstr('What it is: how many moving parts this moment has. Why it matters: lower scores usually want one clean observation, while higher scores can support a twistier line.')})",
|
| 291 |
+
),
|
| 292 |
+
_insight_button(
|
| 293 |
+
f"retrieved context Β· {len(result.retrieved_scenes)}",
|
| 294 |
+
f"wgOpenChip({_jstr('RETRIEVED CONTEXT')},{_jstr('These are Office scenes with a similar comedy pattern. They are reference cases for how the joke works, not lines the coach is copying.')})",
|
| 295 |
+
),
|
| 296 |
+
]
|
| 297 |
+
if result.winning_persona:
|
| 298 |
+
buttons.append(_insight_button("persona lens", _persona_onclick(result.winning_persona)))
|
| 299 |
+
return (
|
| 300 |
+
'<div class="wg-insight-strip">'
|
| 301 |
+
'<div class="wg-insight-title">How WitGym built this line</div>'
|
| 302 |
+
'<div class="wg-insight-sub">Tap any note for a quick explanation.</div>'
|
| 303 |
+
'<div class="wg-insight-buttons">'
|
| 304 |
+
+ "".join(buttons) +
|
| 305 |
+
'</div>'
|
| 306 |
+
'</div>'
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
def _meta_pass1_html(meta) -> str:
|
| 311 |
arc_click = _chip_onclick(meta.archetype.value)
|
| 312 |
ten_click = _chip_onclick(meta.tension_type.value)
|
|
|
|
| 581 |
|
| 582 |
# Winning persona label β use pre-compression value stored in response
|
| 583 |
winning_persona = result.winning_persona
|
| 584 |
+
persona_label = (
|
| 585 |
+
f' Β· <button class="wg-persona-label" type="button" onclick="{_esc(_persona_onclick(winning_persona))}">{_esc(winning_persona)}</button>'
|
| 586 |
+
if winning_persona else ""
|
| 587 |
+
)
|
| 588 |
|
| 589 |
# Another take button (only if alternatives exist)
|
| 590 |
another_take_btn = (
|
|
|
|
| 621 |
]
|
| 622 |
if result.explanation:
|
| 623 |
parts.append(_explanation_panel_html(result.explanation))
|
| 624 |
+
parts.append(_coach_notes_html(result))
|
| 625 |
|
| 626 |
# ββ Drill chips (coaching follow-ups) ββββββββββββββββββββββββββββββββββ
|
| 627 |
if is_last:
|