thumbgate 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/llms.txt +12 -8
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +18 -8
  6. package/adapters/README.md +1 -1
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/codex/config.toml +2 -2
  9. package/adapters/mcp/server-stdio.js +1 -1
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/config/github-about.json +2 -2
  12. package/package.json +158 -10
  13. package/scripts/billing.js +5 -2
  14. package/scripts/statusline.sh +1 -0
  15. package/src/api/server.js +113 -16
  16. package/src/index.js +3 -0
  17. package/.claude-plugin/bundle/icon.png +0 -0
  18. package/.claude-plugin/bundle/icon.svg +0 -18
  19. package/.claude-plugin/bundle/server/index.js +0 -24
  20. package/adapters/chatgpt/INSTALL.md +0 -158
  21. package/adapters/perplexity/.mcp.json +0 -36
  22. package/adapters/perplexity/config.toml +0 -16
  23. package/adapters/perplexity/opencode.json +0 -29
  24. package/bin/memory.sh +0 -64
  25. package/bin/obsidian-sync.sh +0 -20
  26. package/plugins/amp-skill/INSTALL.md +0 -52
  27. package/plugins/amp-skill/SKILL.md +0 -64
  28. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
  29. package/plugins/claude-codex-bridge/.mcp.json +0 -14
  30. package/plugins/claude-codex-bridge/INSTALL.md +0 -43
  31. package/plugins/claude-codex-bridge/README.md +0 -46
  32. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
  33. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
  34. package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
  35. package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
  36. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
  37. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
  38. package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
  39. package/plugins/claude-skill/INSTALL.md +0 -55
  40. package/plugins/claude-skill/SKILL.md +0 -46
  41. package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
  42. package/plugins/codex-profile/.mcp.json +0 -14
  43. package/plugins/codex-profile/AGENTS.md +0 -20
  44. package/plugins/codex-profile/INSTALL.md +0 -89
  45. package/plugins/codex-profile/README.md +0 -61
  46. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
  47. package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
  48. package/plugins/cursor-marketplace/LICENSE +0 -21
  49. package/plugins/cursor-marketplace/README.md +0 -124
  50. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
  51. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  52. package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
  53. package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
  54. package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
  55. package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
  56. package/plugins/cursor-marketplace/mcp.json +0 -14
  57. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
  58. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
  59. package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
  60. package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
  61. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
  62. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
  63. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
  64. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
  65. package/plugins/gemini-extension/INSTALL.md +0 -92
  66. package/plugins/gemini-extension/gemini_prompt.txt +0 -14
  67. package/plugins/gemini-extension/tool_contract.json +0 -45
  68. package/plugins/opencode-profile/INSTALL.md +0 -57
  69. package/public/assets/instagram-card.png +0 -0
  70. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  71. package/public/blog.html +0 -474
  72. package/public/compare/mem0.html +0 -189
  73. package/public/compare/speclock.html +0 -180
  74. package/public/compare.html +0 -310
  75. package/public/dashboard.html +0 -1100
  76. package/public/guide.html +0 -317
  77. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  78. package/public/guides/codex-cli-guardrails.html +0 -158
  79. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  80. package/public/guides/pre-action-gates.html +0 -162
  81. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
  82. package/public/index.html +0 -1225
  83. package/public/js/buyer-intent.js +0 -252
  84. package/public/learn/agent-harness-pattern.html +0 -180
  85. package/public/learn/ai-agent-persistent-memory.html +0 -203
  86. package/public/learn/learn.css +0 -45
  87. package/public/learn/mcp-pre-action-gates-explained.html +0 -172
  88. package/public/learn/stop-ai-agent-force-push.html +0 -134
  89. package/public/learn/vibe-coding-safety-net.html +0 -142
  90. package/public/learn.html +0 -274
  91. package/public/lessons.html +0 -967
  92. package/public/llm-context.md +0 -156
  93. package/public/pro.html +0 -1087
  94. package/public/vercel.json +0 -8
  95. package/scripts/a2ui-engine.js +0 -73
  96. package/scripts/adk-consolidator.js +0 -274
  97. package/scripts/agent-security-hardening.js +0 -225
  98. package/scripts/ai-search-visibility.js +0 -116
  99. package/scripts/autonomous-sales-agent.js +0 -39
  100. package/scripts/autoresearch-runner.js +0 -216
  101. package/scripts/background-agent-governance.js +0 -229
  102. package/scripts/behavioral-extraction.js +0 -93
  103. package/scripts/budget-enforcer.js +0 -173
  104. package/scripts/budget-guard.js +0 -173
  105. package/scripts/build-claude-mcpb.js +0 -255
  106. package/scripts/build-codex-plugin.js +0 -152
  107. package/scripts/capture-railway-diagnostics.sh +0 -97
  108. package/scripts/changeset-check.js +0 -372
  109. package/scripts/check-congruence.js +0 -443
  110. package/scripts/computer-use-firewall.js +0 -280
  111. package/scripts/content-engine/linkedin-content-generator.js +0 -154
  112. package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
  113. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
  114. package/scripts/content-engine/reddit-thread-finder.js +0 -154
  115. package/scripts/context-engine.js +0 -710
  116. package/scripts/daily-digest.js +0 -11
  117. package/scripts/data-governance.js +0 -173
  118. package/scripts/deploy-gcp.sh +0 -44
  119. package/scripts/deploy-policy.js +0 -249
  120. package/scripts/disagreement-mining.js +0 -315
  121. package/scripts/dpo-optimizer.js +0 -206
  122. package/scripts/ensure-repo-bootstrap.js +0 -130
  123. package/scripts/ephemeral-agent-store.js +0 -212
  124. package/scripts/eval-harness.js +0 -56
  125. package/scripts/export-kto-pairs.js +0 -309
  126. package/scripts/export-training.js +0 -446
  127. package/scripts/feedback-fallback.js +0 -111
  128. package/scripts/feedback-inbox-read.js +0 -162
  129. package/scripts/feedback-root-consolidator.js +0 -233
  130. package/scripts/feedback-to-memory.js +0 -185
  131. package/scripts/gate-satisfy.js +0 -42
  132. package/scripts/generate-paperbanana-diagrams.sh +0 -99
  133. package/scripts/generate-pretool-hook.sh +0 -40
  134. package/scripts/github-about.js +0 -430
  135. package/scripts/github-outreach.js +0 -65
  136. package/scripts/gtm-revenue-loop.js +0 -535
  137. package/scripts/hallucination-detector.js +0 -226
  138. package/scripts/hf-papers.js +0 -317
  139. package/scripts/hook-auto-capture.sh +0 -100
  140. package/scripts/hook-stop-pr-thread-check.sh +0 -68
  141. package/scripts/hook-stop-self-score.sh +0 -51
  142. package/scripts/hook-stop-verify-deploy.sh +0 -31
  143. package/scripts/hook-verify-before-done.sh +0 -20
  144. package/scripts/managed-dpo-export.js +0 -91
  145. package/scripts/markdown-escape.js +0 -12
  146. package/scripts/marketing-experiment.js +0 -657
  147. package/scripts/memalign-recall.js +0 -111
  148. package/scripts/memory-migration.js +0 -296
  149. package/scripts/meta-policy.js +0 -190
  150. package/scripts/metered-billing.js +0 -16
  151. package/scripts/model-tier-router.js +0 -310
  152. package/scripts/money-watcher.js +0 -218
  153. package/scripts/multi-hop-recall.js +0 -240
  154. package/scripts/per-step-scoring.js +0 -163
  155. package/scripts/perplexity-command-center.js +0 -644
  156. package/scripts/perplexity-marketing.js +0 -454
  157. package/scripts/pii-scanner.js +0 -153
  158. package/scripts/plan-gate.js +0 -154
  159. package/scripts/post-everywhere.js +0 -341
  160. package/scripts/post-to-x-retry.sh +0 -22
  161. package/scripts/post-to-x.js +0 -369
  162. package/scripts/pr-manager.js +0 -421
  163. package/scripts/principle-extractor.js +0 -162
  164. package/scripts/pro-features.js +0 -41
  165. package/scripts/prompt-dlp.js +0 -222
  166. package/scripts/prove-adapters.js +0 -860
  167. package/scripts/prove-attribution.js +0 -361
  168. package/scripts/prove-automation.js +0 -651
  169. package/scripts/prove-autoresearch.js +0 -304
  170. package/scripts/prove-claim-verification.js +0 -277
  171. package/scripts/prove-cloudflare-sandbox.js +0 -161
  172. package/scripts/prove-data-pipeline.js +0 -408
  173. package/scripts/prove-data-quality.js +0 -227
  174. package/scripts/prove-evolution.js +0 -352
  175. package/scripts/prove-harnesses.js +0 -287
  176. package/scripts/prove-intelligence.js +0 -257
  177. package/scripts/prove-lancedb.js +0 -425
  178. package/scripts/prove-local-intelligence.js +0 -340
  179. package/scripts/prove-loop-closure.js +0 -263
  180. package/scripts/prove-packaged-runtime.js +0 -327
  181. package/scripts/prove-predictive-insights.js +0 -355
  182. package/scripts/prove-runtime.js +0 -363
  183. package/scripts/prove-seo-gsd.js +0 -234
  184. package/scripts/prove-settings.js +0 -279
  185. package/scripts/prove-subway-upgrades.js +0 -277
  186. package/scripts/prove-tessl.js +0 -229
  187. package/scripts/prove-training-export.js +0 -325
  188. package/scripts/prove-workflow-contract.js +0 -112
  189. package/scripts/prove-xmemory.js +0 -332
  190. package/scripts/publish-decision.js +0 -159
  191. package/scripts/ralph-loop.js +0 -376
  192. package/scripts/ralph-mode-ci.js +0 -434
  193. package/scripts/reddit-dm-outreach.js +0 -192
  194. package/scripts/reddit-monitor-cron.sh +0 -26
  195. package/scripts/reminder-engine.js +0 -132
  196. package/scripts/revenue-status.js +0 -472
  197. package/scripts/rotate-stripe-webhook-secret.js +0 -314
  198. package/scripts/schedule-manager.js +0 -249
  199. package/scripts/self-healing-check.js +0 -193
  200. package/scripts/session-analyzer.js +0 -533
  201. package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
  202. package/scripts/skill-exporter.js +0 -260
  203. package/scripts/skill-materializer.js +0 -134
  204. package/scripts/skill-packs.js +0 -136
  205. package/scripts/skill-proposer.js +0 -99
  206. package/scripts/skill-quality-tracker.js +0 -282
  207. package/scripts/slow-loop.js +0 -72
  208. package/scripts/social-analytics/db/marketing-db.js +0 -179
  209. package/scripts/social-analytics/db/schema.sql +0 -55
  210. package/scripts/social-analytics/digest.js +0 -256
  211. package/scripts/social-analytics/engagement-audit.js +0 -185
  212. package/scripts/social-analytics/generate-instagram-card.js +0 -123
  213. package/scripts/social-analytics/generate-slides.js +0 -268
  214. package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
  215. package/scripts/social-analytics/install-growth-automation.js +0 -114
  216. package/scripts/social-analytics/load-env.js +0 -77
  217. package/scripts/social-analytics/mcp-server.js +0 -289
  218. package/scripts/social-analytics/normalizer.js +0 -580
  219. package/scripts/social-analytics/notify.js +0 -162
  220. package/scripts/social-analytics/poll-all.js +0 -107
  221. package/scripts/social-analytics/pollers/github.js +0 -195
  222. package/scripts/social-analytics/pollers/instagram.js +0 -253
  223. package/scripts/social-analytics/pollers/linkedin.js +0 -340
  224. package/scripts/social-analytics/pollers/plausible.js +0 -245
  225. package/scripts/social-analytics/pollers/reddit.js +0 -306
  226. package/scripts/social-analytics/pollers/threads.js +0 -233
  227. package/scripts/social-analytics/pollers/tiktok.js +0 -203
  228. package/scripts/social-analytics/pollers/x.js +0 -227
  229. package/scripts/social-analytics/pollers/youtube.js +0 -304
  230. package/scripts/social-analytics/pollers/zernio.js +0 -183
  231. package/scripts/social-analytics/post-video.js +0 -316
  232. package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
  233. package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
  234. package/scripts/social-analytics/publishers/devto.js +0 -122
  235. package/scripts/social-analytics/publishers/instagram.js +0 -317
  236. package/scripts/social-analytics/publishers/linkedin.js +0 -294
  237. package/scripts/social-analytics/publishers/reddit.js +0 -385
  238. package/scripts/social-analytics/publishers/threads.js +0 -275
  239. package/scripts/social-analytics/publishers/tiktok.js +0 -217
  240. package/scripts/social-analytics/publishers/x.js +0 -259
  241. package/scripts/social-analytics/publishers/youtube.js +0 -223
  242. package/scripts/social-analytics/publishers/zernio.js +0 -568
  243. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
  244. package/scripts/social-analytics/run-digest.js +0 -34
  245. package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
  246. package/scripts/social-analytics/store.js +0 -455
  247. package/scripts/social-analytics/sync-launch-assets.js +0 -185
  248. package/scripts/social-analytics/utm.js +0 -143
  249. package/scripts/social-pipeline.js +0 -2626
  250. package/scripts/social-post-hourly.js +0 -228
  251. package/scripts/social-quality-gate.js +0 -134
  252. package/scripts/social-reply-monitor.js +0 -592
  253. package/scripts/status-dashboard.js +0 -155
  254. package/scripts/stripe-live-status.js +0 -115
  255. package/scripts/subagent-profiles.js +0 -79
  256. package/scripts/sync-branch-protection.js +0 -340
  257. package/scripts/sync-gh-secrets-from-env.sh +0 -70
  258. package/scripts/sync-github-about.js +0 -55
  259. package/scripts/sync-version.js +0 -479
  260. package/scripts/synthetic-dpo.js +0 -234
  261. package/scripts/tessl-export.js +0 -369
  262. package/scripts/test-coverage.js +0 -128
  263. package/scripts/thumbgate-bench.js +0 -494
  264. package/scripts/thumbgate_session_start.sh +0 -32
  265. package/scripts/train_from_feedback.py +0 -929
  266. package/scripts/validate-feedback.js +0 -581
  267. package/scripts/verify-obsidian-setup.sh +0 -269
  268. package/scripts/verify-run.js +0 -269
  269. package/scripts/weekly-auto-post.js +0 -124
  270. package/scripts/x-autonomous-marketing.js +0 -139
@@ -1,967 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>ThumbGate — Lessons Learned</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
- <style>
9
- *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
10
- :root {
11
- --bg: #0a0a0b; --bg-raised: #111113; --bg-card: #161618; --border: #222225;
12
- --text: #e8e8ec; --text-muted: #8b8b96; --cyan: #22d3ee;
13
- --cyan-dim: rgba(34,211,238,0.12); --green: #4ade80; --red: #f87171;
14
- --yellow: #fbbf24; --purple: #a78bfa;
15
- --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif;
16
- --mono: 'SF Mono', 'Cascadia Code', 'JetBrains Mono', 'Fira Code', Consolas, monospace;
17
- }
18
- html { scroll-behavior: smooth; }
19
- body { font-family: var(--font); background: var(--bg); color: var(--text); line-height: 1.6; -webkit-font-smoothing: antialiased; }
20
- .container { max-width: 1060px; margin: 0 auto; padding: 0 24px; }
21
-
22
- /* NAV */
23
- nav { position: sticky; top: 0; z-index: 50; background: rgba(10,10,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); padding: 14px 0; }
24
- nav .container { display: flex; justify-content: space-between; align-items: center; }
25
- .nav-logo { font-weight: 700; font-size: 15px; color: var(--text); text-decoration: none; }
26
- .nav-links { display: flex; gap: 16px; align-items: center; }
27
- .nav-links a { color: var(--text-muted); text-decoration: none; font-size: 13px; }
28
- .nav-links a:hover { color: var(--text); }
29
- .nav-links a.active { color: var(--cyan); }
30
-
31
- /* STATS */
32
- .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 32px 0; }
33
- .stat-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; transition: border-color 0.15s, transform 0.1s; cursor: pointer; }
34
- .stat-card:hover { border-color: rgba(34,211,238,0.4); transform: translateY(-2px); }
35
- .stat-card:active { transform: translateY(0); border-color: rgba(34,211,238,0.7); }
36
- .stat-card.selected { border-color: rgba(34,211,238,0.6); background: rgba(34,211,238,0.05); }
37
- .stat-label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; }
38
- .stat-value { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; }
39
- .stat-value.green { color: var(--green); }
40
- .stat-value.red { color: var(--red); }
41
- .stat-value.cyan { color: var(--cyan); }
42
- .stat-value.purple { color: var(--purple); }
43
- .stat-sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
44
-
45
- /* IMPROVEMENT METRICS */
46
- .metrics-panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 22px; margin: 0 0 24px; }
47
- .metrics-header { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; margin-bottom: 18px; }
48
- .metrics-header h2 { margin-bottom: 6px; }
49
- .metrics-header p { font-size: 13px; color: var(--text-muted); max-width: 720px; }
50
- .metrics-summary-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 18px; }
51
- .metric-tile { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px; padding: 16px; min-height: 94px; }
52
- .metric-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 6px; }
53
- .metric-number { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
54
- .metric-number.green { color: var(--green); }
55
- .metric-number.red { color: var(--red); }
56
- .metric-number.cyan { color: var(--cyan); }
57
- .metric-number.purple { color: var(--purple); }
58
- .metric-note { font-size: 12px; color: var(--text-muted); margin-top: 6px; }
59
- .metrics-chart-wrap { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
60
- .metrics-chart-title { display: flex; justify-content: space-between; gap: 12px; align-items: center; margin-bottom: 12px; }
61
- .metrics-chart-title h3 { font-size: 14px; margin: 0; }
62
- .metrics-chart-title p { font-size: 12px; color: var(--text-muted); }
63
- .metrics-legend { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
64
- .metrics-legend span { display: inline-flex; align-items: center; gap: 6px; }
65
- .metrics-legend-dot { width: 8px; height: 8px; border-radius: 999px; display: inline-block; }
66
- .metrics-legend-dot.up { background: var(--green); }
67
- .metrics-legend-dot.down { background: var(--red); }
68
- .metrics-legend-dot.deny { background: var(--cyan); }
69
- .metrics-legend-dot.warn { background: var(--yellow); }
70
- .metrics-chart { display: grid; grid-template-columns: repeat(14, minmax(0, 1fr)); gap: 8px; align-items: end; min-height: 180px; }
71
- .metrics-bar { background: transparent; border: 1px solid transparent; border-radius: 10px; padding: 8px 4px; color: inherit; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; }
72
- .metrics-bar:hover { border-color: rgba(34,211,238,0.35); background: rgba(34,211,238,0.04); }
73
- .metrics-bar.selected { border-color: rgba(34,211,238,0.65); background: rgba(34,211,238,0.08); }
74
- .metrics-bar-body { width: 100%; display: flex; justify-content: center; align-items: flex-end; gap: 6px; }
75
- .metrics-bar-stack { width: 100%; max-width: 28px; height: 120px; display: flex; flex-direction: column; justify-content: flex-end; gap: 3px; }
76
- .metrics-bar-stack.gate { max-width: 16px; height: 84px; }
77
- .metrics-bar-empty { width: 100%; border-radius: 8px; background: rgba(255,255,255,0.04); border: 1px dashed rgba(255,255,255,0.05); flex: 1; }
78
- .metrics-segment { width: 100%; border-radius: 8px; min-height: 0; }
79
- .metrics-segment.up { background: linear-gradient(180deg, rgba(74,222,128,0.95), rgba(74,222,128,0.55)); }
80
- .metrics-segment.down { background: linear-gradient(180deg, rgba(248,113,113,0.95), rgba(248,113,113,0.55)); }
81
- .metrics-segment.deny { background: linear-gradient(180deg, rgba(34,211,238,0.95), rgba(34,211,238,0.55)); }
82
- .metrics-segment.warn { background: linear-gradient(180deg, rgba(251,191,36,0.95), rgba(251,191,36,0.55)); }
83
- .metrics-bar-total { font-size: 12px; font-weight: 600; color: var(--text); }
84
- .metrics-bar-subtotal { font-size: 10px; color: var(--text-muted); line-height: 1; }
85
- .metrics-bar-label { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
86
- .metrics-chart-note { font-size: 12px; color: var(--text-muted); margin-top: 12px; }
87
-
88
- /* TABS */
89
- .tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
90
- .tab { padding: 12px 20px; font-size: 14px; font-weight: 500; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
91
- .tab:hover { color: var(--text); }
92
- .tab.active { color: var(--cyan); border-bottom-color: var(--cyan); }
93
- .tab-content { display: none; }
94
- .tab-content.active { display: block; }
95
-
96
- /* SEARCH */
97
- .search-bar { display: flex; gap: 12px; margin-bottom: 16px; }
98
- .search-input { flex: 1; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; color: var(--text); font-size: 15px; }
99
- .search-input:focus { outline: none; border-color: var(--cyan); }
100
- .search-input::placeholder { color: var(--text-muted); }
101
- .btn { background: var(--cyan); color: var(--bg); padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; font-size: 13px; cursor: pointer; }
102
- .btn:hover { opacity: 0.85; }
103
- .filter-row { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
104
- .filter-btn { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 6px; padding: 6px 14px; color: var(--text-muted); font-size: 12px; cursor: pointer; }
105
- .filter-btn:hover, .filter-btn.active { border-color: var(--cyan); color: var(--cyan); }
106
-
107
- /* RULE CARDS */
108
- .rules-list { display: flex; flex-direction: column; gap: 12px; }
109
- .rule-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 20px; transition: border-color 0.15s; }
110
- .rule-card:hover { border-color: rgba(34,211,238,0.3); }
111
- .rule-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; gap: 12px; }
112
- .rule-title { font-size: 15px; font-weight: 600; }
113
- .rule-severity { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.05em; }
114
- .rule-severity.critical { background: rgba(248,113,113,0.15); color: var(--red); }
115
- .rule-severity.warning { background: rgba(251,191,36,0.15); color: var(--yellow); }
116
- .rule-severity.info { background: var(--cyan-dim); color: var(--cyan); }
117
- .rule-context { font-size: 13px; color: var(--text-muted); line-height: 1.5; margin-bottom: 10px; }
118
- .rule-meta { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; }
119
- .rule-meta-item { font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 4px; }
120
- .rule-meta-item .value { color: var(--text); font-weight: 600; }
121
- .rule-meta-item .value.green { color: var(--green); }
122
- .rule-effectiveness { font-size: 12px; font-weight: 600; color: var(--green); background: rgba(74,222,128,0.1); padding: 2px 8px; border-radius: 4px; }
123
- .rule-tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
124
- .tag { font-size: 11px; background: var(--cyan-dim); color: var(--cyan); padding: 2px 8px; border-radius: 4px; }
125
-
126
- /* TIMELINE */
127
- .timeline { position: relative; padding-left: 24px; }
128
- .timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0; width: 2px; background: var(--border); }
129
- .timeline-item { position: relative; margin-bottom: 20px; }
130
- .timeline-dot { position: absolute; left: -20px; top: 6px; width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--bg); }
131
- .timeline-dot.up { background: var(--green); }
132
- .timeline-dot.down { background: var(--red); }
133
- .timeline-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
134
- .timeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
135
- .timeline-signal { font-size: 13px; font-weight: 600; }
136
- .timeline-signal.up { color: var(--green); }
137
- .timeline-signal.down { color: var(--red); }
138
- .timeline-date { font-size: 12px; color: var(--text-muted); }
139
- .timeline-context { font-size: 13px; color: var(--text-muted); }
140
- .timeline-learned { font-size: 12px; color: var(--cyan); margin-top: 6px; }
141
-
142
- /* INSIGHTS */
143
- .insight-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 24px; margin-bottom: 16px; }
144
- .insight-card h3 { font-size: 15px; margin-bottom: 12px; }
145
- .insight-card .insight-list { list-style: none; }
146
- .insight-card .insight-list li { font-size: 13px; color: var(--text-muted); padding: 8px 0; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
147
- .insight-card .insight-list li:last-child { border-bottom: none; }
148
- .pro-badge { background: linear-gradient(135deg, var(--cyan-dim), rgba(167,139,250,0.12)); border: 1px solid rgba(34,211,238,0.3); border-radius: 10px; padding: 20px; text-align: center; margin: 20px 0; }
149
- .pro-badge h3 { color: var(--cyan); margin-bottom: 8px; }
150
- .pro-badge p { font-size: 13px; color: var(--text-muted); margin-bottom: 16px; }
151
- .pro-badge a { color: var(--bg); background: var(--cyan); padding: 10px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 14px; }
152
- .pro-badge a:hover { opacity: 0.85; }
153
-
154
- .empty { text-align: center; padding: 48px; color: var(--text-muted); font-size: 15px; }
155
- .loading { text-align: center; padding: 24px; color: var(--text-muted); }
156
- h2 { font-size: 20px; font-weight: 700; margin-bottom: 16px; letter-spacing: -0.02em; }
157
-
158
- @media (max-width: 700px) {
159
- .stats-grid { grid-template-columns: repeat(2, 1fr); }
160
- .metrics-header { flex-direction: column; }
161
- .metrics-summary-grid { grid-template-columns: repeat(2, 1fr); }
162
- .metrics-chart { grid-template-columns: repeat(7, minmax(0, 1fr)); row-gap: 12px; }
163
- .filter-row { flex-wrap: wrap; }
164
- .rule-meta { flex-direction: column; gap: 8px; }
165
- }
166
-
167
- /* Feedback widget */
168
- .feedback-widget { position: fixed; bottom: 20px; right: 20px; z-index: 1000; }
169
- .feedback-toggle { background: var(--cyan); color: var(--bg); border: none; border-radius: 50px; padding: 12px 20px; font-size: 14px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(34,211,238,0.3); transition: transform 0.15s; }
170
- .feedback-toggle:hover { transform: scale(1.05); }
171
- .feedback-panel { display: none; position: absolute; bottom: 56px; right: 0; width: 340px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); }
172
- .feedback-panel.open { display: block; }
173
- .feedback-panel h3 { font-size: 15px; margin-bottom: 12px; }
174
- .feedback-panel textarea { width: 100%; min-height: 80px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); padding: 10px; font-size: 13px; font-family: inherit; resize: vertical; }
175
- .feedback-panel textarea:focus { outline: none; border-color: var(--cyan); }
176
- .feedback-panel select { width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); padding: 8px 10px; font-size: 13px; margin: 8px 0; }
177
- .feedback-submit { background: var(--cyan); color: var(--bg); border: none; border-radius: 8px; padding: 10px 20px; font-size: 13px; font-weight: 600; cursor: pointer; width: 100%; margin-top: 8px; }
178
- .feedback-submit:hover { opacity: 0.85; }
179
- .feedback-submit:disabled { opacity: 0.5; cursor: not-allowed; }
180
- .feedback-success { color: var(--green); font-size: 13px; text-align: center; padding: 12px 0; }
181
-
182
- </style>
183
- </head>
184
- <body>
185
-
186
- <nav>
187
- <div class="container">
188
- <a href="/dashboard" class="nav-logo">👍👎 ThumbGate</a>
189
- <div class="nav-links">
190
- <a href="/dashboard">Dashboard</a>
191
- <a href="/lessons" class="active">Lessons</a>
192
- <a href="/">Landing Page</a>
193
- <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
194
- </div>
195
- </div>
196
- </nav>
197
-
198
- <div class="container">
199
- <div style="margin:32px 0 24px;padding:24px;background:linear-gradient(135deg,rgba(167,139,250,0.08),rgba(34,211,238,0.05));border:1px solid rgba(167,139,250,0.2);border-radius:12px;">
200
- <h1 style="font-size:22px;font-weight:700;margin-bottom:8px;letter-spacing:-0.02em;">📚 Lessons Learned</h1>
201
- <p style="font-size:14px;color:var(--text-muted);line-height:1.6;max-width:700px;">See what ThumbGate learned from your feedback, which failure patterns keep repeating, and how many actions the gate layer actually blocked. <span style="color:var(--purple);font-weight:600;">This view separates repeated failures from recorded gate blocks so the proof stays honest.</span></p>
202
- <div style="display:flex;gap:16px;margin-top:12px;font-size:12px;color:var(--text-muted);">
203
- <span>📋 <strong style="color:var(--text);">Active Rules</strong> — what was learned</span>
204
- <span>📊 <strong style="color:var(--text);">Timeline</strong> — when it was learned</span>
205
- <span>💡 <strong style="color:var(--text);">Insights</strong> — what to do next</span>
206
- </div>
207
- </div>
208
- <div class="stats-grid">
209
- <div class="stat-card" onclick="switchTab('rules'); filterSeverity('all')">
210
- <div class="stat-label">Active Rules</div>
211
- <div class="stat-value cyan" id="statRules">0</div>
212
- <div class="stat-sub">Auto-generated from feedback</div>
213
- </div>
214
- <div class="stat-card" onclick="switchTab('rules'); filterSeverity('critical')">
215
- <div class="stat-label">Critical</div>
216
- <div class="stat-value red" id="statCritical">0</div>
217
- <div class="stat-sub">Highest severity rules</div>
218
- </div>
219
- <div class="stat-card" onclick="switchTab('timeline')">
220
- <div class="stat-label">Actions Blocked</div>
221
- <div class="stat-value green" id="statBlocked">0</div>
222
- <div class="stat-sub">Recorded gate denies, not inferred repeats</div>
223
- </div>
224
- <div class="stat-card" onclick="switchTab('insights')">
225
- <div class="stat-label">Approval Trend</div>
226
- <div class="stat-value purple" id="statTrend">--</div>
227
- <div class="stat-sub" id="statTrendSub">7-day rolling</div>
228
- </div>
229
- </div>
230
-
231
- <div id="lessonsMode" style="margin:-8px 0 20px;padding:12px 16px;border:1px solid var(--border);border-radius:10px;background:var(--bg-card);font-size:13px;color:var(--text-muted);">
232
- Loading lessons view...
233
- </div>
234
-
235
- <div class="metrics-panel" id="metricsPanel">
236
- <div class="metrics-header">
237
- <div>
238
- <h2>Improvement Over Time</h2>
239
- <p id="metricsSummary">Loading defensible live metrics...</p>
240
- </div>
241
- <button class="filter-btn" id="metricsClearBtn" type="button" style="display:none;" onclick="clearTimelineDayFilter()">Clear day filter</button>
242
- </div>
243
- <div class="metrics-summary-grid" id="metricsSummaryGrid">
244
- <div class="metric-tile">
245
- <div class="metric-kicker">Fast path rate</div>
246
- <div class="metric-number purple">--</div>
247
- <div class="metric-note">Waiting for live decisions</div>
248
- </div>
249
- <div class="metric-tile">
250
- <div class="metric-kicker">Override rate</div>
251
- <div class="metric-number red">--</div>
252
- <div class="metric-note">Waiting for live decisions</div>
253
- </div>
254
- <div class="metric-tile">
255
- <div class="metric-kicker">Rollback rate</div>
256
- <div class="metric-number cyan">--</div>
257
- <div class="metric-note">Waiting for live decisions</div>
258
- </div>
259
- <div class="metric-tile">
260
- <div class="metric-kicker">Median latency</div>
261
- <div class="metric-number green">--</div>
262
- <div class="metric-note">Time from recommendation to recorded outcome</div>
263
- </div>
264
- </div>
265
- <div class="metrics-chart-wrap">
266
- <div class="metrics-chart-title">
267
- <h3>Recent Feedback + Gate Activity</h3>
268
- <p>Click any day to inspect the recorded feedback behind the trend and compare it with gate interceptions.</p>
269
- </div>
270
- <div class="metrics-legend">
271
- <span><i class="metrics-legend-dot up"></i> Positive feedback</span>
272
- <span><i class="metrics-legend-dot down"></i> Negative feedback</span>
273
- <span><i class="metrics-legend-dot deny"></i> Gate deny</span>
274
- <span><i class="metrics-legend-dot warn"></i> Gate warn</span>
275
- </div>
276
- <div class="metrics-chart" id="metricsChart">
277
- <div class="loading" style="grid-column:1 / -1;">Loading chart...</div>
278
- </div>
279
- <div class="metrics-chart-note" id="metricsChartNote">The chart uses recorded feedback events, not inferred rule strength.</div>
280
- </div>
281
- </div>
282
-
283
- <div class="tabs">
284
- <div class="tab active" onclick="switchTab('rules')">📋 Active Rules</div>
285
- <div class="tab" onclick="switchTab('timeline')">📊 Feedback Timeline</div>
286
- <div class="tab" onclick="switchTab('insights')">💡 Insights <span style="font-size:10px;color:var(--purple);font-weight:700;">PRO</span></div>
287
- </div>
288
-
289
- <!-- TAB 1: ACTIVE RULES -->
290
- <div class="tab-content active" id="tab-rules">
291
- <div class="search-bar">
292
- <input type="text" class="search-input" id="ruleSearch" placeholder="Search rules... (try: git, push, test, env)" onkeydown="if(event.key==='Enter')searchRules()">
293
- <button class="btn" onclick="searchRules()">Search</button>
294
- </div>
295
- <div class="filter-row">
296
- <button class="filter-btn active" onclick="filterSeverity('all', this)">All</button>
297
- <button class="filter-btn" onclick="filterSeverity('critical', this)">Critical</button>
298
- <button class="filter-btn" onclick="filterSeverity('warning', this)">Warning</button>
299
- <button class="filter-btn" onclick="filterSeverity('info', this)">Info</button>
300
- </div>
301
- <div class="rules-list" id="rulesList">
302
- <div class="loading">Loading rules...</div>
303
- </div>
304
- </div>
305
-
306
- <!-- TAB 2: FEEDBACK TIMELINE -->
307
- <div class="tab-content" id="tab-timeline">
308
- <div class="filter-row">
309
- <button class="filter-btn active" onclick="filterTimeline('all', this)">All</button>
310
- <button class="filter-btn" onclick="filterTimeline('up', this)">👍 Positive</button>
311
- <button class="filter-btn" onclick="filterTimeline('down', this)">👎 Negative</button>
312
- </div>
313
- <div class="timeline" id="timelineList">
314
- <div class="loading">Loading timeline...</div>
315
- </div>
316
- </div>
317
-
318
- <!-- TAB 3: INSIGHTS (PRO) -->
319
- <div class="tab-content" id="tab-insights">
320
- <div class="insight-card">
321
- <h3>🔥 Top Recurring Failure Patterns</h3>
322
- <ul class="insight-list" id="insightMistakes">
323
- <li><span>Loading...</span></li>
324
- </ul>
325
- </div>
326
- <div class="insight-card">
327
- <h3>🛡️ Most Reinforced Rules</h3>
328
- <ul class="insight-list" id="insightEffective">
329
- <li><span>Loading...</span></li>
330
- </ul>
331
- </div>
332
- <div class="insight-card">
333
- <h3>🗑️ Stale Rules (no triggers in 30 days)</h3>
334
- <ul class="insight-list" id="insightStale">
335
- <li><span>Loading...</span></li>
336
- </ul>
337
- </div>
338
- <div class="pro-badge" id="proBadge" style="display:none;">
339
- <h3>Unlock Full Insights</h3>
340
- <p>DPO training data export, effectiveness charts, pattern clustering, weekly auto-digests, and a learned intervention policy trained from feedback, audits, and diagnostics.</p>
341
- <a href="/#pricing">Get Pro — $19/mo</a>
342
- </div>
343
- </div>
344
- </div>
345
-
346
- <script>
347
- var allRules = [];
348
- var allTimeline = [];
349
- var isDemo = true;
350
- var API_KEY = '';
351
- var dashboardSnapshot = null;
352
- var activeTimelineSignal = 'all';
353
- var activeTimelineDay = null;
354
- var currentImprovementSeries = [];
355
- const BOOTSTRAP_API_KEY = __LESSONS_BOOTSTRAP_KEY__;
356
- const LOCAL_PRO_BOOTSTRAP = __LESSONS_BOOTSTRAP_ENABLED__;
357
-
358
- function escHtml(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
359
- function escAttr(s) { return escHtml(String(s || '')).replace(/"/g, '&quot;'); }
360
-
361
- function getHeaders() {
362
- return { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' };
363
- }
364
-
365
- function hasBootstrapKey() {
366
- return LOCAL_PRO_BOOTSTRAP && Boolean(BOOTSTRAP_API_KEY);
367
- }
368
-
369
- function setMode(message, demoMode) {
370
- isDemo = demoMode;
371
- var el = document.getElementById('lessonsMode');
372
- if (!el) return;
373
- el.textContent = message;
374
- el.style.borderColor = demoMode ? 'var(--border)' : 'rgba(74,222,128,0.25)';
375
- el.style.color = demoMode ? 'var(--text-muted)' : 'var(--green)';
376
- }
377
-
378
- function formatDate(value) {
379
- if (!value) return '';
380
- var ts = new Date(value);
381
- if (Number.isNaN(ts.getTime())) return String(value);
382
- return ts.toLocaleString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });
383
- }
384
-
385
- function formatShortDay(dayKey) {
386
- if (!dayKey) return '';
387
- var ts = new Date(dayKey + 'T00:00:00');
388
- if (Number.isNaN(ts.getTime())) return dayKey;
389
- return ts.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
390
- }
391
-
392
- function normalizeSignal(value) {
393
- var signal = String(value || '').toLowerCase();
394
- if (signal === 'up' || signal === 'positive' || signal === 'thumbs_up') return 'up';
395
- return 'down';
396
- }
397
-
398
- function toDayKey(value) {
399
- if (!value) return '';
400
- var ts = new Date(value);
401
- if (Number.isNaN(ts.getTime())) return '';
402
- var year = ts.getFullYear();
403
- var month = String(ts.getMonth() + 1).padStart(2, '0');
404
- var day = String(ts.getDate()).padStart(2, '0');
405
- return year + '-' + month + '-' + day;
406
- }
407
-
408
- function highlightCard(index) {
409
- document.querySelectorAll('.stat-card').forEach(function(c, i) {
410
- c.classList.toggle('selected', i === index);
411
- });
412
- }
413
- function switchTab(name) {
414
- // Highlight the correct tab header (only inside .tabs, not stat cards)
415
- document.querySelectorAll('.tabs .tab').forEach(function(t) { t.classList.remove('active'); });
416
- document.querySelectorAll('.tab-content').forEach(function(c) { c.classList.remove('active'); });
417
- var tabHeaders = document.querySelectorAll('.tabs .tab');
418
- var tabMap = { rules: 0, timeline: 1, insights: 2 };
419
- if (tabHeaders[tabMap[name]]) tabHeaders[tabMap[name]].classList.add('active');
420
- var content = document.getElementById('tab-' + name);
421
- if (content) content.classList.add('active');
422
- // Highlight the corresponding stat card
423
- var cardMap = { rules: 0, timeline: 2, insights: 3 };
424
- highlightCard(cardMap[name] !== undefined ? cardMap[name] : -1);
425
- if (typeof plausible === 'function') plausible('lessons_tab', { props: { tab: name } });
426
- }
427
-
428
- function filterSeverity(level, el) {
429
- // Update filter button active state
430
- var filterBtns = document.querySelectorAll('#tab-rules .filter-btn');
431
- filterBtns.forEach(function(b) { b.classList.remove('active'); });
432
- if (el) {
433
- el.classList.add('active');
434
- } else {
435
- // Find the matching button and activate it
436
- filterBtns.forEach(function(b) {
437
- if (b.textContent.trim().toLowerCase() === level || (level === 'all' && b.textContent.trim() === 'All')) {
438
- b.classList.add('active');
439
- }
440
- });
441
- }
442
- // Highlight Critical card (index 1) when filtering critical, else Active Rules card (index 0)
443
- if (level === 'critical') { highlightCard(1); } else { highlightCard(0); }
444
- var filtered = level === 'all' ? allRules : allRules.filter(function(r) { return r.severity === level; });
445
- renderRules(filtered);
446
- }
447
-
448
- function filterTimeline(signal, el) {
449
- activeTimelineSignal = signal;
450
- if (el) {
451
- document.querySelectorAll('#tab-timeline .filter-btn').forEach(function(b) { b.classList.remove('active'); });
452
- el.classList.add('active');
453
- }
454
- renderTimeline(getFilteredTimelineItems());
455
- updateMetricsSelection();
456
- }
457
-
458
- function getFilteredTimelineItems() {
459
- return allTimeline.filter(function(item) {
460
- if (activeTimelineSignal !== 'all' && item.signal !== activeTimelineSignal) return false;
461
- if (activeTimelineDay && item.dayKey !== activeTimelineDay) return false;
462
- return true;
463
- });
464
- }
465
-
466
- function focusTimelineDay(dayKey) {
467
- activeTimelineDay = dayKey;
468
- switchTab('timeline');
469
- renderTimeline(getFilteredTimelineItems());
470
- updateMetricsSelection();
471
- }
472
-
473
- function clearTimelineDayFilter() {
474
- activeTimelineDay = null;
475
- renderTimeline(getFilteredTimelineItems());
476
- updateMetricsSelection();
477
- }
478
-
479
- function updateMetricsSelection() {
480
- document.querySelectorAll('.metrics-bar').forEach(function(bar) {
481
- bar.classList.toggle('selected', activeTimelineDay && bar.getAttribute('data-day-key') === activeTimelineDay);
482
- });
483
- var clearBtn = document.getElementById('metricsClearBtn');
484
- if (clearBtn) clearBtn.style.display = activeTimelineDay ? 'inline-flex' : 'none';
485
- var note = document.getElementById('metricsChartNote');
486
- if (!note) return;
487
- if (activeTimelineDay) {
488
- var selected = currentImprovementSeries.find(function(item) { return item.dayKey === activeTimelineDay; }) || null;
489
- if (selected) {
490
- note.textContent = 'Showing timeline events for ' + formatShortDay(activeTimelineDay) + '. Feedback: ' + selected.total + ' (' + selected.up + ' positive, ' + selected.down + ' negative). Gate audit: ' + selected.intercepted + ' intercepted (' + selected.deny + ' denies, ' + selected.warn + ' warns).';
491
- } else {
492
- note.textContent = 'Showing timeline events for ' + formatShortDay(activeTimelineDay) + '. Clear the day filter to return to the full timeline.';
493
- }
494
- } else {
495
- note.textContent = 'The chart combines recorded feedback events with daily gate-audit activity. Decision-loop metrics above come from recorded evaluations and outcomes.';
496
- }
497
- }
498
-
499
- function searchRules() {
500
- var q = document.getElementById('ruleSearch').value.toLowerCase().trim();
501
- if (!q) { renderRules(allRules); return; }
502
- var filtered = allRules.filter(function(r) {
503
- return (r.title || '').toLowerCase().includes(q) ||
504
- (r.context || '').toLowerCase().includes(q) ||
505
- (r.tags || []).some(function(t) { return t.toLowerCase().includes(q); });
506
- });
507
- renderRules(filtered);
508
- }
509
-
510
- function getSeverity(rule) {
511
- if (rule.occurrences >= 10 || (rule.tags || []).includes('CRITICAL')) return 'critical';
512
- if (rule.occurrences >= 3) return 'warning';
513
- return 'info';
514
- }
515
-
516
- function renderRules(rules) {
517
- var el = document.getElementById('rulesList');
518
- if (!rules.length) { el.innerHTML = '<div class="empty">No rules match your filter</div>'; return; }
519
- el.innerHTML = rules.map(function(r) {
520
- var sev = r.severity || getSeverity(r);
521
- return '<div class="rule-card" data-rule-id="' + escHtml(r.id || '') + '">' +
522
- '<div class="rule-header">' +
523
- '<div class="rule-title"><a href="/lessons/' + encodeURIComponent(r.id || '') + '" style="color:inherit;text-decoration:none" onmouseover="this.style.color=\'var(--cyan)\'" onmouseout="this.style.color=\'inherit\'">' + escHtml(r.title || r.id || 'Rule') + '</a></div>' +
524
- '<span class="rule-severity ' + sev + '">' + sev + '</span>' +
525
- '</div>' +
526
- (r.context ? '<div class="rule-context">' + escHtml(r.context) + '</div>' : '') +
527
- '<div class="rule-meta">' +
528
- '<div class="rule-meta-item">Seen in feedback <span class="value">' + (r.occurrences || 0) + '</span> times</div>' +
529
- (r.lastTriggered ? '<div class="rule-meta-item">Last: <span class="value">' + escHtml(r.lastTriggered) + '</span></div>' : '') +
530
- '</div>' +
531
- (r.tags && r.tags.length ? '<div class="rule-tags">' + r.tags.map(function(t) { return '<span class="tag">' + escHtml(t) + '</span>'; }).join('') + '</div>' : '') +
532
- '</div>';
533
- }).join('');
534
- }
535
-
536
- function renderTimeline(items) {
537
- var el = document.getElementById('timelineList');
538
- if (!items.length) { el.innerHTML = '<div class="empty">No feedback events</div>'; return; }
539
- el.innerHTML = items.map(function(item) {
540
- var sig = item.signal || 'down';
541
- var emoji = sig === 'up' ? '👍' : '👎';
542
- var fbId = item.feedbackId || item.id || '';
543
- return '<div class="timeline-item" data-feedback-id="' + escHtml(fbId) + '">' +
544
- '<div class="timeline-dot ' + sig + '"></div>' +
545
- '<div class="timeline-card">' +
546
- '<div class="timeline-header">' +
547
- '<span class="timeline-signal ' + sig + '">' + emoji + ' ' + (sig === 'up' ? 'Positive' : 'Negative') + '</span>' +
548
- '<span class="timeline-date">' + escHtml(item.date || '') + '</span>' +
549
- '</div>' +
550
- '<div class="timeline-context"><a href="/lessons/' + encodeURIComponent(item.feedbackId || '') + '" style="color:inherit;text-decoration:none" onmouseover="this.style.color=\'var(--cyan)\'" onmouseout="this.style.color=\'inherit\'">' + escHtml(item.context || item.title || '') + '</a></div>' +
551
- (item.learned ? '<div class="timeline-learned">→ Rule created: ' + escHtml(item.learned) + '</div>' : '') +
552
- '</div>' +
553
- '</div>';
554
- }).join('');
555
- }
556
-
557
- function renderInsights(data) {
558
- var mistakes = document.getElementById('insightMistakes');
559
- var effective = document.getElementById('insightEffective');
560
- var stale = document.getElementById('insightStale');
561
-
562
- // Dedup by title — keep the one with highest occurrences
563
- var seen = {};
564
- var dedupedRules = allRules.filter(function(r) {
565
- var key = (r.title || '').trim().toLowerCase();
566
- if (!key) return false;
567
- if (seen[key] && seen[key].occurrences >= (r.occurrences || 0)) return false;
568
- seen[key] = r;
569
- return true;
570
- });
571
-
572
- var topMistakes = dedupedRules.filter(function(r) { return r.severity === 'critical'; })
573
- .sort(function(a, b) { return (b.occurrences || 0) - (a.occurrences || 0); }).slice(0, 5);
574
- var topEffective = dedupedRules.slice().sort(function(a, b) { return (b.occurrences || 0) - (a.occurrences || 0); })
575
- .filter(function(r) { return (r.occurrences || 0) > 1; }).slice(0, 5);
576
- var staleRules = dedupedRules.filter(function(r) { return !r.lastTriggered || r.lastTriggered === 'never'; });
577
-
578
- function insightRow(r, badge) {
579
- var title = r.title.length > 80 ? r.title.slice(0, 77) + '...' : r.title;
580
- return '<li style="cursor:pointer" onclick="switchTab(\'rules\'); searchAndHighlight(\'' + escHtml(r.id) + '\')">'
581
- + '<span title="' + escHtml(r.title) + '">' + escHtml(title) + '</span>' + badge + '</li>';
582
- }
583
-
584
- mistakes.innerHTML = topMistakes.length ? topMistakes.map(function(r) {
585
- return insightRow(r, '<span style="color:var(--red);font-weight:600">' + (r.occurrences || 0) + 'x</span>');
586
- }).join('') : '<li><span style="color:var(--green)">No critical repeated failures this week</span></li>';
587
-
588
- effective.innerHTML = topEffective.length ? topEffective.map(function(r) {
589
- return insightRow(r, '<span class="rule-effectiveness">Seen ' + (r.occurrences || 0) + 'x</span>');
590
- }).join('') : '<li><span>No reinforced rules yet</span></li>';
591
-
592
- stale.innerHTML = staleRules.length ? staleRules.map(function(r) {
593
- return insightRow(r, '<span style="color:var(--text-muted)">Archive?</span>');
594
- }).join('') : '<li><span style="color:var(--green)">All rules are active</span></li>';
595
- }
596
-
597
- function buildFeedbackSeries(items, dayCount) {
598
- var series = [];
599
- var today = new Date();
600
- today.setHours(0, 0, 0, 0);
601
- var counts = {};
602
- items.forEach(function(item) {
603
- if (!item.dayKey) return;
604
- if (!counts[item.dayKey]) counts[item.dayKey] = { up: 0, down: 0 };
605
- counts[item.dayKey][item.signal] = (counts[item.dayKey][item.signal] || 0) + 1;
606
- });
607
- for (var idx = dayCount - 1; idx >= 0; idx--) {
608
- var day = new Date(today);
609
- day.setDate(today.getDate() - idx);
610
- var dayKey = toDayKey(day.toISOString());
611
- var entry = counts[dayKey] || { up: 0, down: 0 };
612
- series.push({
613
- dayKey: dayKey,
614
- label: formatShortDay(dayKey),
615
- up: entry.up || 0,
616
- down: entry.down || 0,
617
- total: (entry.up || 0) + (entry.down || 0)
618
- });
619
- }
620
- return series;
621
- }
622
-
623
- function renderImprovementMetrics(stats, data) {
624
- var summary = document.getElementById('metricsSummary');
625
- var grid = document.getElementById('metricsSummaryGrid');
626
- var chart = document.getElementById('metricsChart');
627
- if (!summary || !grid || !chart) return;
628
-
629
- if (isDemo) {
630
- summary.textContent = 'Live improvement trends appear here once local Pro is connected. The decision tiles are based on recorded evaluations plus override and rollback outcomes, while the chart below stays anchored to feedback and gate audits.';
631
- grid.innerHTML = [
632
- { label: 'Fast path rate', value: '62%', tone: 'purple', note: 'Sample auto-execute share' },
633
- { label: 'Override rate', value: '14%', tone: 'red', note: 'Sample operator overrides' },
634
- { label: 'Rollback rate', value: '4%', tone: 'cyan', note: 'Sample reversed decisions' },
635
- { label: 'Median latency', value: '6m', tone: 'green', note: 'Sample decision completion speed' }
636
- ].map(function(tile) {
637
- return '<div class="metric-tile"><div class="metric-kicker">' + escHtml(tile.label) + '</div><div class="metric-number ' + escHtml(tile.tone) + '">' + escHtml(tile.value) + '</div><div class="metric-note">' + escHtml(tile.note) + '</div></div>';
638
- }).join('');
639
- currentImprovementSeries = [];
640
- chart.innerHTML = '<div class="empty" style="grid-column:1 / -1;">Upgrade to Pro to see the clickable live feedback and gate-audit timeline.</div>';
641
- updateMetricsSelection();
642
- return;
643
- }
644
-
645
- var gateStats = (data && data.gateStats) || {};
646
- var gateAudit = (data && data.gateAudit) || {};
647
- var liveMetrics = (data && data.liveMetrics) || {};
648
- var decisionLoop = (data && data.decisions) || {};
649
- var harness = (data && data.harness) || {};
650
- var approvalRate = Math.round(((stats && (stats.recentRate || stats.approvalRate)) || 0) * 100);
651
- var errorTrend = liveMetrics.errorTrend || {};
652
- var fastPathRate = Math.round(((decisionLoop.fastPathRate || 0) * 100));
653
- var overrideRate = Math.round(((decisionLoop.overrideRate || 0) * 100));
654
- var rollbackRate = Math.round(((decisionLoop.rollbackRate || 0) * 100));
655
- var medianLatencyMs = Number(decisionLoop.medianLatencyMs || 0);
656
- var medianLatencyText = medianLatencyMs >= 3600000
657
- ? (medianLatencyMs / 3600000).toFixed(1) + 'h'
658
- : medianLatencyMs >= 60000
659
- ? Math.round(medianLatencyMs / 60000) + 'm'
660
- : Math.round(medianLatencyMs / 1000) + 's';
661
- var thisWeekNeg = errorTrend.thisWeek || 0;
662
- var lastWeekNeg = errorTrend.lastWeek || 0;
663
- var blocked = gateStats.blocked || 0;
664
- var resolvedCount = decisionLoop.resolvedCount || 0;
665
-
666
- summary.textContent = 'ThumbGate is auto-routing ' + fastPathRate + '% of tracked decisions while holding override rate at ' + overrideRate + '% and rollback rate at ' + rollbackRate + '% across '
667
- + resolvedCount + ' resolved decision' + (resolvedCount === 1 ? '' : 's') + '. Feedback approval is ' + approvalRate + '%, and gates still recorded ' + blocked + ' deny decision' + (blocked === 1 ? '' : 's') + '.';
668
-
669
- grid.innerHTML = [
670
- { label: 'Fast path rate', value: fastPathRate + '%', tone: 'purple', note: 'Recorded evaluations that stayed auto-executable' },
671
- { label: 'Override rate', value: overrideRate + '%', tone: 'red', note: 'Resolved decisions later changed by a human' },
672
- { label: 'Rollback rate', value: rollbackRate + '%', tone: 'cyan', note: 'Resolved decisions later reversed' },
673
- { label: 'Median latency', value: medianLatencyText, tone: 'green', note: 'Time from recommendation to recorded outcome' }
674
- ].map(function(tile) {
675
- return '<div class="metric-tile"><div class="metric-kicker">' + escHtml(tile.label) + '</div><div class="metric-number ' + escHtml(tile.tone) + '">' + escHtml(tile.value) + '</div><div class="metric-note">' + escHtml(tile.note) + '</div></div>';
676
- }).join('');
677
-
678
- var gateDays = {};
679
- ((gateAudit.days) || []).forEach(function(day) {
680
- gateDays[day.dayKey] = day;
681
- });
682
- var series = buildFeedbackSeries(allTimeline, 14).map(function(item) {
683
- var gateDay = gateDays[item.dayKey] || { deny: 0, warn: 0, intercepted: 0 };
684
- return {
685
- dayKey: item.dayKey,
686
- label: item.label,
687
- up: item.up,
688
- down: item.down,
689
- total: item.total,
690
- deny: gateDay.deny || 0,
691
- warn: gateDay.warn || 0,
692
- intercepted: gateDay.intercepted || 0
693
- };
694
- });
695
- currentImprovementSeries = series;
696
- var maxFeedbackTotal = series.reduce(function(max, item) { return Math.max(max, item.total); }, 0);
697
- var maxGateTotal = series.reduce(function(max, item) { return Math.max(max, item.intercepted); }, 0);
698
- if (maxFeedbackTotal === 0 && maxGateTotal === 0) {
699
- currentImprovementSeries = [];
700
- chart.innerHTML = '<div class="empty" style="grid-column:1 / -1;">No recent feedback events to chart yet.</div>';
701
- updateMetricsSelection();
702
- return;
703
- }
704
-
705
- chart.innerHTML = series.map(function(item) {
706
- if (!item.total && !item.intercepted) {
707
- return '<button class="metrics-bar" type="button" data-day-key="' + escAttr(item.dayKey) + '" onclick="focusTimelineDay(\'' + escAttr(item.dayKey) + '\')">' +
708
- '<div class="metrics-bar-body"><div class="metrics-bar-stack"><div class="metrics-bar-empty"></div></div><div class="metrics-bar-stack gate"><div class="metrics-bar-empty"></div></div></div>' +
709
- '<div class="metrics-bar-total">F0</div>' +
710
- '<div class="metrics-bar-subtotal">G0</div>' +
711
- '<div class="metrics-bar-label">' + escHtml(item.label) + '</div>' +
712
- '</button>';
713
- }
714
- var feedbackHeight = item.total > 0 ? Math.max(14, Math.round((item.total / Math.max(maxFeedbackTotal, 1)) * 120)) : 0;
715
- var upHeight = item.up > 0 ? Math.max(8, Math.round((item.up / Math.max(item.total, 1)) * feedbackHeight)) : 0;
716
- var downHeight = item.down > 0 ? Math.max(8, feedbackHeight - upHeight) : 0;
717
- if (item.up > 0 && item.down > 0 && upHeight + downHeight > 120) downHeight = Math.max(8, 120 - upHeight);
718
- var gateHeight = item.intercepted > 0 ? Math.max(12, Math.round((item.intercepted / Math.max(maxGateTotal, 1)) * 84)) : 0;
719
- var denyHeight = item.deny > 0 ? Math.max(8, Math.round((item.deny / Math.max(item.intercepted, 1)) * gateHeight)) : 0;
720
- var warnHeight = item.warn > 0 ? Math.max(8, gateHeight - denyHeight) : 0;
721
- if (item.deny > 0 && item.warn > 0 && denyHeight + warnHeight > 84) warnHeight = Math.max(8, 84 - denyHeight);
722
- return '<button class="metrics-bar" type="button" data-day-key="' + escAttr(item.dayKey) + '" onclick="focusTimelineDay(\'' + escAttr(item.dayKey) + '\')">' +
723
- '<div class="metrics-bar-body">' +
724
- '<div class="metrics-bar-stack">' +
725
- (item.up ? '<div class="metrics-segment up" style="height:' + upHeight + 'px" title="' + item.up + ' positive feedback"></div>' : '') +
726
- (item.down ? '<div class="metrics-segment down" style="height:' + downHeight + 'px" title="' + item.down + ' negative feedback"></div>' : '') +
727
- (!item.total ? '<div class="metrics-bar-empty"></div>' : '') +
728
- '</div>' +
729
- '<div class="metrics-bar-stack gate">' +
730
- (item.deny ? '<div class="metrics-segment deny" style="height:' + denyHeight + 'px" title="' + item.deny + ' gate denies"></div>' : '') +
731
- (item.warn ? '<div class="metrics-segment warn" style="height:' + warnHeight + 'px" title="' + item.warn + ' gate warns"></div>' : '') +
732
- (!item.intercepted ? '<div class="metrics-bar-empty"></div>' : '') +
733
- '</div>' +
734
- '</div>' +
735
- '<div class="metrics-bar-total">F' + item.total + '</div>' +
736
- '<div class="metrics-bar-subtotal">G' + item.intercepted + '</div>' +
737
- '<div class="metrics-bar-label">' + escHtml(item.label) + '</div>' +
738
- '</button>';
739
- }).join('');
740
- updateMetricsSelection();
741
- }
742
-
743
- function searchAndHighlight(ruleId) {
744
- var el = document.querySelector('[data-rule-id="' + ruleId + '"]');
745
- if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.style.borderColor = 'var(--cyan)'; setTimeout(function() { el.style.borderColor = ''; }, 2000); }
746
- }
747
-
748
- function deriveSeverity(lesson) {
749
- var severity = String(lesson.importance || '').toLowerCase();
750
- if (severity === 'critical' || severity === 'high') return 'critical';
751
- if (severity === 'medium') return 'warning';
752
- return 'info';
753
- }
754
-
755
- function mapLiveRules(payload) {
756
- return (payload.results || []).map(function(result) {
757
- var linkedGate = (((result.systemResponse || {}).linkedAutoGates) || [])[0] || null;
758
- var sourceFeedback = ((result.systemResponse || {}).sourceFeedback) || null;
759
- return {
760
- id: result.id,
761
- title: result.title || ((result.lesson || {}).summary) || 'Lesson',
762
- context: ((result.lesson || {}).howToAvoid) || ((result.lesson || {}).summary) || (sourceFeedback && sourceFeedback.context) || '',
763
- occurrences: linkedGate && linkedGate.occurrences ? linkedGate.occurrences : 1,
764
- severity: deriveSeverity(result),
765
- tags: Array.isArray(result.tags) ? result.tags : [],
766
- lastTriggered: sourceFeedback && sourceFeedback.timestamp ? formatDate(sourceFeedback.timestamp) : formatDate(result.timestamp),
767
- sourceFeedbackId: sourceFeedback && sourceFeedback.id ? sourceFeedback.id : result.sourceFeedbackId
768
- };
769
- });
770
- }
771
-
772
- function mapTimelineItems(payload, lessonMap) {
773
- return (payload.results || []).map(function(entry) {
774
- var linkedLesson = lessonMap.get(entry.id) || null;
775
- var timestamp = entry.timestamp || null;
776
- return {
777
- signal: normalizeSignal(entry.signal || entry.feedback),
778
- context: entry.context || entry.title || '',
779
- date: formatDate(entry.timestamp),
780
- timestamp: timestamp,
781
- dayKey: toDayKey(timestamp),
782
- learned: linkedLesson ? linkedLesson.title : '',
783
- feedbackId: entry.id || ''
784
- };
785
- });
786
- }
787
-
788
- // Demo data
789
- var demoRules = [
790
- { id: 'execution-gap', title: 'Push before claiming done', context: 'After tests pass, immediately git add + commit + push. Never announce completion without pushing.', occurrences: 36, prevented: 22, severity: 'critical', tags: ['execution-gap', 'git-workflow'], lastTriggered: '2 hours ago' },
791
- { id: 'anti-lying', title: 'Never claim without evidence', context: 'Check with tools before claiming anything. Show evidence. Say "I don\'t know" instead of guessing.', occurrences: 24, prevented: 15, severity: 'critical', tags: ['anti-lying', 'trust'], lastTriggered: '1 hour ago' },
792
- { id: 'speed', title: 'Switch strategy after first failure', context: 'Prefer text-based assertions over slow UI tools. No repeated retries.', occurrences: 21, prevented: 12, severity: 'critical', tags: ['speed', 'efficiency'], lastTriggered: '3 hours ago' },
793
- { id: 'pr-review', title: 'Check PR state after every push', context: 'Run gh pr view after push. Code changes alone do NOT resolve conversations.', occurrences: 15, prevented: 8, severity: 'warning', tags: ['pr-review', 'verification'], lastTriggered: '5 hours ago' },
794
- { id: 'git-workflow', title: 'Tests pass then add-commit-push', context: 'Never announce completion without pushing. Verify git diff HEAD origin is empty.', occurrences: 12, prevented: 7, severity: 'warning', tags: ['git-workflow', 'push'], lastTriggered: 'yesterday' },
795
- { id: 'delegation', title: 'Execute, don\'t ask permission', context: 'On clear instructions, execute immediately. Never ask "want me to do X?"', occurrences: 11, prevented: 6, severity: 'warning', tags: ['delegation', 'autonomy'], lastTriggered: 'yesterday' },
796
- { id: 'config-before-code', title: 'curl first, never edit .env', context: 'On API failures: curl endpoint, check .env, check Postman, check Slack. Never modify code for auth issues.', occurrences: 4, prevented: 3, severity: 'info', tags: ['config', 'api', 'auth'], lastTriggered: '3 days ago' },
797
- { id: 'jest-hoisting', title: 'No const/let before jest.mock()', context: 'jest.mock() is hoisted. Use captured-props pattern instead.', occurrences: 2, prevented: 1, severity: 'info', tags: ['jest', 'testing'], lastTriggered: '1 week ago' },
798
- ];
799
-
800
- var demoTimeline = [
801
- { signal: 'down', context: 'Claimed fix done without pushing code', date: '2 hours ago', learned: 'Push before claiming done' },
802
- { signal: 'down', context: 'Said endpoint not deployed without checking', date: '3 hours ago', learned: 'Never claim without evidence' },
803
- { signal: 'up', context: 'Clean 4-tier pricing implementation with tests', date: '5 hours ago' },
804
- { signal: 'down', context: 'Made false claims about what was broken', date: '6 hours ago', learned: 'Never claim without evidence' },
805
- { signal: 'up', context: 'Statusline reorder done correctly first try', date: '8 hours ago' },
806
- { signal: 'down', context: 'Delegated to subagent without verifying output', date: 'yesterday', learned: 'Execute, don\'t ask permission' },
807
- { signal: 'up', context: 'Evidence-based PR management with iterative fixes', date: 'yesterday' },
808
- { signal: 'down', context: 'Spent 45min on code when curl would have diagnosed in 30s', date: '3 days ago', learned: 'curl first, never edit .env' },
809
- ];
810
-
811
- function renderUpgradeWall(containerId) {
812
- var el = document.getElementById(containerId);
813
- if (!el) return;
814
- var wall = document.createElement('div');
815
- wall.style.cssText = 'position:relative;margin-top:8px;';
816
- wall.innerHTML = '<div style="position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;z-index:2;">' +
817
- '<div style="text-align:center;background:rgba(10,10,15,0.92);border:1px solid #333;border-radius:12px;padding:28px 36px;">' +
818
- '<div style="font-size:20px;font-weight:700;color:#fff;margin-bottom:8px;">Unlock your full lessons</div>' +
819
- '<div style="color:#aaa;margin-bottom:16px;">Pro shows your real prevention rules, timeline, and insights.</div>' +
820
- '<a href="https://buy.stripe.com/5kQ4gzbmI9Lo6tPayn3sI06" target="_blank" rel="noopener" ' +
821
- 'style="display:inline-block;background:#b85c2d;color:#fff;padding:10px 24px;border-radius:8px;text-decoration:none;font-weight:700;">Upgrade to Pro — $19/mo</a>' +
822
- '<div style="color:#666;font-size:12px;margin-top:10px;">npx thumbgate pro --activate --key=YOUR_KEY</div>' +
823
- '</div></div>';
824
- el.appendChild(wall);
825
- }
826
-
827
- function loadDemo() {
828
- allRules = demoRules.slice(0, 3);
829
- allTimeline = demoTimeline.slice(0, 3);
830
- dashboardSnapshot = null;
831
- activeTimelineSignal = 'all';
832
- activeTimelineDay = null;
833
-
834
- document.getElementById('statRules').textContent = demoRules.length;
835
- document.getElementById('statCritical').textContent = demoRules.filter(function(r) { return r.severity === 'critical'; }).length;
836
- document.getElementById('statBlocked').textContent = demoRules.reduce(function(sum, r) { return sum + (r.prevented || 0); }, 0);
837
- document.getElementById('statTrend').textContent = '12.5%';
838
- document.getElementById('statTrendSub').textContent = '7-day approval rate';
839
-
840
- renderRules(allRules);
841
- renderTimeline(allTimeline);
842
- renderInsights();
843
- renderImprovementMetrics({ recentRate: 0.125 }, null);
844
- renderUpgradeWall('rulesList');
845
- renderUpgradeWall('timelineList');
846
- setMode('Demo preview — upgrade to Pro to see your live lessons, timeline, and insights.', true);
847
- document.getElementById('proBadge').style.display = 'block';
848
- }
849
-
850
- async function loadLive() {
851
- if (!hasBootstrapKey()) {
852
- loadDemo();
853
- return;
854
- }
855
- API_KEY = String(BOOTSTRAP_API_KEY || '').trim();
856
- try {
857
- const responses = await Promise.all([
858
- fetch('/v1/feedback/stats', { headers: getHeaders() }),
859
- fetch('/v1/lessons/search?limit=50', { headers: getHeaders() }),
860
- fetch('/v1/search?q=*&limit=40&source=feedback', { headers: getHeaders() }),
861
- fetch('/v1/dashboard', { headers: getHeaders() })
862
- ]);
863
- if (responses.some(function(res) { return !res.ok; })) {
864
- throw new Error('Local Pro bootstrap unavailable');
865
- }
866
-
867
- const stats = await responses[0].json();
868
- const lessonsPayload = await responses[1].json();
869
- const feedbackPayload = await responses[2].json();
870
- dashboardSnapshot = await responses[3].json();
871
-
872
- allRules = mapLiveRules(lessonsPayload);
873
- var lessonMap = new Map(allRules
874
- .filter(function(rule) { return rule.sourceFeedbackId; })
875
- .map(function(rule) { return [rule.sourceFeedbackId, rule]; }));
876
- allTimeline = mapTimelineItems(feedbackPayload, lessonMap);
877
- activeTimelineSignal = 'all';
878
- activeTimelineDay = null;
879
-
880
- document.getElementById('statRules').textContent = allRules.length;
881
- document.getElementById('statCritical').textContent = allRules.filter(function(r) { return r.severity === 'critical'; }).length;
882
- document.getElementById('statBlocked').textContent = ((dashboardSnapshot.gateStats || {}).blocked || 0);
883
- document.getElementById('statTrend').textContent = ((stats.recentRate || stats.approvalRate || 0) * 100).toFixed(1) + '%';
884
- document.getElementById('statTrendSub').textContent = 'Live approval rate';
885
-
886
- renderRules(allRules);
887
- renderTimeline(allTimeline);
888
- renderInsights(dashboardSnapshot);
889
- renderImprovementMetrics(stats, dashboardSnapshot);
890
- document.getElementById('proBadge').style.display = 'none';
891
- setMode('Local Pro connected — this lessons view is reading your live rules, feedback timeline, and insights.', false);
892
- } catch (_err) {
893
- loadDemo();
894
- }
895
- }
896
-
897
- loadLive().then(function() {
898
- // Default: highlight Active Rules card on page load
899
- highlightCard(0);
900
-
901
- // Handle #feedbackId hash — scroll to and highlight the matching item
902
- var hash = window.location.hash.replace('#', '');
903
- if (!hash) return;
904
- // Try rule cards first
905
- var el = document.querySelector('[data-rule-id="' + hash + '"]') ||
906
- document.querySelector('[data-feedback-id="' + hash + '"]');
907
- if (!el) {
908
- // Search in timeline by switching tab
909
- switchTab('timeline');
910
- el = document.querySelector('[data-feedback-id="' + hash + '"]');
911
- }
912
- if (el) {
913
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
914
- el.style.borderColor = 'var(--cyan)';
915
- el.style.boxShadow = '0 0 12px rgba(34,211,238,0.3)';
916
- setTimeout(function() { el.style.borderColor = ''; el.style.boxShadow = ''; }, 4000);
917
- }
918
- });
919
- </script>
920
-
921
- <div class="feedback-widget">
922
- <div class="feedback-panel" id="feedbackPanel">
923
- <h3>Share feedback with ThumbGate team</h3>
924
- <select id="feedbackCategory">
925
- <option value="bug">Bug report</option>
926
- <option value="feature">Feature request</option>
927
- <option value="question">Question</option>
928
- </select>
929
- <textarea id="feedbackText" placeholder="What's on your mind? Be honest — we read every one."></textarea>
930
- <button class="feedback-submit" id="feedbackSubmit" onclick="submitFeedback()">Submit</button>
931
- <div id="feedbackResult"></div>
932
- </div>
933
- <button class="feedback-toggle" onclick="toggleFeedback()">💬 Feedback</button>
934
- </div>
935
- <script>
936
- function toggleFeedback() {
937
- document.getElementById('feedbackPanel').classList.toggle('open');
938
- document.getElementById('feedbackResult').innerHTML = '';
939
- }
940
- async function submitFeedback() {
941
- var btn = document.getElementById('feedbackSubmit');
942
- var text = document.getElementById('feedbackText').value.trim();
943
- var category = document.getElementById('feedbackCategory').value;
944
- if (!text) return;
945
- btn.disabled = true; btn.textContent = 'Submitting...';
946
- try {
947
- var res = await fetch('/api/feedback/submit', {
948
- method: 'POST',
949
- headers: { 'Content-Type': 'application/json' },
950
- body: JSON.stringify({ category: category, message: text })
951
- });
952
- var data = await res.json();
953
- if (data.success) {
954
- document.getElementById('feedbackResult').innerHTML = '<div class="feedback-success">Thanks! Filed as <a href="' + data.issueUrl + '" target="_blank" style="color:var(--cyan)">#' + data.issueNumber + '</a></div>';
955
- document.getElementById('feedbackText').value = '';
956
- } else {
957
- document.getElementById('feedbackResult').innerHTML = '<div style="color:var(--red);font-size:13px">Failed: ' + (data.error || 'unknown') + '</div>';
958
- }
959
- } catch (e) {
960
- document.getElementById('feedbackResult').innerHTML = '<div style="color:var(--red);font-size:13px">Network error. Try again.</div>';
961
- }
962
- btn.disabled = false; btn.textContent = 'Submit';
963
- }
964
- </script>
965
-
966
- </body>
967
- </html>