thumbgate 0.9.9

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 (369) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1483 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +286 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1195 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +293 -0
  117. package/scripts/auto-wire-hooks.js +316 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +93 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +231 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +206 -0
  152. package/scripts/ensure-repo-bootstrap.js +129 -0
  153. package/scripts/ephemeral-agent-store.js +219 -0
  154. package/scripts/eval-harness.js +56 -0
  155. package/scripts/evolution-state.js +241 -0
  156. package/scripts/experiment-tracker.js +267 -0
  157. package/scripts/export-databricks-bundle.js +242 -0
  158. package/scripts/export-dpo-pairs.js +344 -0
  159. package/scripts/export-kto-pairs.js +309 -0
  160. package/scripts/export-training.js +450 -0
  161. package/scripts/failure-diagnostics.js +558 -0
  162. package/scripts/feedback-attribution.js +313 -0
  163. package/scripts/feedback-fallback.js +110 -0
  164. package/scripts/feedback-history-distiller.js +391 -0
  165. package/scripts/feedback-inbox-read.js +162 -0
  166. package/scripts/feedback-loop.js +1887 -0
  167. package/scripts/feedback-paths.js +145 -0
  168. package/scripts/feedback-quality.js +139 -0
  169. package/scripts/feedback-root-consolidator.js +238 -0
  170. package/scripts/feedback-schema.js +426 -0
  171. package/scripts/feedback-session.js +286 -0
  172. package/scripts/feedback-to-memory.js +185 -0
  173. package/scripts/feedback-to-rules.js +164 -0
  174. package/scripts/filesystem-search.js +405 -0
  175. package/scripts/funnel-analytics.js +35 -0
  176. package/scripts/gate-satisfy.js +42 -0
  177. package/scripts/gate-stats.js +116 -0
  178. package/scripts/gate-templates.js +70 -0
  179. package/scripts/gates-engine.js +816 -0
  180. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  181. package/scripts/generate-pretool-hook.sh +40 -0
  182. package/scripts/github-about.js +350 -0
  183. package/scripts/github-outreach.js +65 -0
  184. package/scripts/gtm-revenue-loop.js +520 -0
  185. package/scripts/hallucination-detector.js +226 -0
  186. package/scripts/hf-papers.js +317 -0
  187. package/scripts/history-distiller.js +200 -0
  188. package/scripts/hook-auto-capture.sh +100 -0
  189. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  190. package/scripts/hook-stop-self-score.sh +51 -0
  191. package/scripts/hook-stop-verify-deploy.sh +31 -0
  192. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  193. package/scripts/hook-verify-before-done.sh +20 -0
  194. package/scripts/hosted-config.js +156 -0
  195. package/scripts/hybrid-feedback-context.js +675 -0
  196. package/scripts/install-mcp.js +159 -0
  197. package/scripts/intent-router.js +392 -0
  198. package/scripts/internal-agent-bootstrap.js +490 -0
  199. package/scripts/jsonl-watcher.js +155 -0
  200. package/scripts/lesson-db.js +613 -0
  201. package/scripts/lesson-inference.js +310 -0
  202. package/scripts/lesson-retrieval.js +95 -0
  203. package/scripts/lesson-rotation.js +137 -0
  204. package/scripts/lesson-search.js +644 -0
  205. package/scripts/lesson-synthesis.js +196 -0
  206. package/scripts/license.js +50 -0
  207. package/scripts/local-model-profile.js +384 -0
  208. package/scripts/markdown-escape.js +12 -0
  209. package/scripts/marketing-experiment.js +671 -0
  210. package/scripts/mcp-config.js +149 -0
  211. package/scripts/mcp-policy.js +99 -0
  212. package/scripts/memalign-recall.js +111 -0
  213. package/scripts/memory-firewall.js +222 -0
  214. package/scripts/memory-migration.js +296 -0
  215. package/scripts/meta-policy.js +190 -0
  216. package/scripts/metered-billing.js +16 -0
  217. package/scripts/model-tier-router.js +301 -0
  218. package/scripts/money-watcher.js +71 -0
  219. package/scripts/multi-hop-recall.js +240 -0
  220. package/scripts/natural-language-harness.js +330 -0
  221. package/scripts/obsidian-export.js +713 -0
  222. package/scripts/operational-dashboard.js +103 -0
  223. package/scripts/operational-summary.js +93 -0
  224. package/scripts/optimize-context.js +17 -0
  225. package/scripts/org-dashboard.js +201 -0
  226. package/scripts/partner-orchestration.js +146 -0
  227. package/scripts/per-step-scoring.js +165 -0
  228. package/scripts/perplexity-marketing.js +466 -0
  229. package/scripts/pii-scanner.js +153 -0
  230. package/scripts/plan-gate.js +154 -0
  231. package/scripts/post-everywhere.js +308 -0
  232. package/scripts/post-to-x-retry.sh +22 -0
  233. package/scripts/post-to-x.js +369 -0
  234. package/scripts/pr-manager.js +236 -0
  235. package/scripts/predictive-insights.js +356 -0
  236. package/scripts/principle-extractor.js +162 -0
  237. package/scripts/pro-features.js +40 -0
  238. package/scripts/pro-local-dashboard.js +174 -0
  239. package/scripts/problem-detail.js +53 -0
  240. package/scripts/product-feedback.js +134 -0
  241. package/scripts/profile-router.js +245 -0
  242. package/scripts/prompt-dlp.js +221 -0
  243. package/scripts/prompt-guard.js +83 -0
  244. package/scripts/prove-adapters.js +863 -0
  245. package/scripts/prove-attribution.js +365 -0
  246. package/scripts/prove-automation.js +653 -0
  247. package/scripts/prove-autoresearch.js +304 -0
  248. package/scripts/prove-claim-verification.js +277 -0
  249. package/scripts/prove-cloudflare-sandbox.js +163 -0
  250. package/scripts/prove-data-pipeline.js +410 -0
  251. package/scripts/prove-data-quality.js +227 -0
  252. package/scripts/prove-evolution.js +352 -0
  253. package/scripts/prove-harnesses.js +287 -0
  254. package/scripts/prove-intelligence.js +259 -0
  255. package/scripts/prove-lancedb.js +371 -0
  256. package/scripts/prove-local-intelligence.js +342 -0
  257. package/scripts/prove-loop-closure.js +263 -0
  258. package/scripts/prove-predictive-insights.js +357 -0
  259. package/scripts/prove-runtime.js +350 -0
  260. package/scripts/prove-seo-gsd.js +234 -0
  261. package/scripts/prove-settings.js +279 -0
  262. package/scripts/prove-subway-upgrades.js +277 -0
  263. package/scripts/prove-tessl.js +229 -0
  264. package/scripts/prove-training-export.js +327 -0
  265. package/scripts/prove-workflow-contract.js +116 -0
  266. package/scripts/prove-xmemory.js +332 -0
  267. package/scripts/publish-decision.js +133 -0
  268. package/scripts/pulse.js +80 -0
  269. package/scripts/rate-limiter.js +125 -0
  270. package/scripts/reddit-dm-outreach.js +182 -0
  271. package/scripts/reddit-monitor-cron.sh +26 -0
  272. package/scripts/reflector-agent.js +221 -0
  273. package/scripts/reminder-engine.js +132 -0
  274. package/scripts/revenue-status.js +472 -0
  275. package/scripts/risk-scorer.js +459 -0
  276. package/scripts/rlaif-self-audit.js +129 -0
  277. package/scripts/rlhf_session_start.sh +32 -0
  278. package/scripts/rubric-engine.js +230 -0
  279. package/scripts/schedule-manager.js +251 -0
  280. package/scripts/secret-scanner.js +414 -0
  281. package/scripts/self-heal.js +147 -0
  282. package/scripts/self-healing-check.js +188 -0
  283. package/scripts/semantic-layer.js +98 -0
  284. package/scripts/seo-gsd.js +1153 -0
  285. package/scripts/settings-hierarchy.js +214 -0
  286. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  287. package/scripts/skill-exporter.js +262 -0
  288. package/scripts/skill-generator.js +446 -0
  289. package/scripts/skill-materializer.js +134 -0
  290. package/scripts/skill-packs.js +136 -0
  291. package/scripts/skill-proposer.js +99 -0
  292. package/scripts/skill-quality-tracker.js +282 -0
  293. package/scripts/slo-alert-engine.js +14 -0
  294. package/scripts/slow-loop.js +72 -0
  295. package/scripts/social-analytics/db/schema.sql +32 -0
  296. package/scripts/social-analytics/db/social-analytics.db +0 -0
  297. package/scripts/social-analytics/digest.js +256 -0
  298. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  299. package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
  300. package/scripts/social-analytics/load-env.js +46 -0
  301. package/scripts/social-analytics/mcp-server.js +289 -0
  302. package/scripts/social-analytics/normalizer.js +580 -0
  303. package/scripts/social-analytics/notify.js +162 -0
  304. package/scripts/social-analytics/poll-all.js +92 -0
  305. package/scripts/social-analytics/pollers/github.js +195 -0
  306. package/scripts/social-analytics/pollers/instagram.js +253 -0
  307. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  308. package/scripts/social-analytics/pollers/plausible.js +247 -0
  309. package/scripts/social-analytics/pollers/reddit.js +306 -0
  310. package/scripts/social-analytics/pollers/threads.js +233 -0
  311. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  312. package/scripts/social-analytics/pollers/x.js +227 -0
  313. package/scripts/social-analytics/pollers/youtube.js +304 -0
  314. package/scripts/social-analytics/pollers/zernio.js +183 -0
  315. package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
  316. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  317. package/scripts/social-analytics/publishers/devto.js +122 -0
  318. package/scripts/social-analytics/publishers/instagram.js +317 -0
  319. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  320. package/scripts/social-analytics/publishers/reddit.js +390 -0
  321. package/scripts/social-analytics/publishers/threads.js +275 -0
  322. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  323. package/scripts/social-analytics/publishers/x.js +259 -0
  324. package/scripts/social-analytics/publishers/youtube.js +223 -0
  325. package/scripts/social-analytics/publishers/zernio.js +378 -0
  326. package/scripts/social-analytics/run-digest.js +34 -0
  327. package/scripts/social-analytics/store.js +257 -0
  328. package/scripts/social-analytics/utm.js +143 -0
  329. package/scripts/social-pipeline.js +2628 -0
  330. package/scripts/social-quality-gate.js +18 -0
  331. package/scripts/social-reply-monitor.js +445 -0
  332. package/scripts/status-dashboard.js +155 -0
  333. package/scripts/statusline-lesson.js +16 -0
  334. package/scripts/statusline-tower.js +8 -0
  335. package/scripts/statusline.sh +116 -0
  336. package/scripts/stripe-live-status.js +115 -0
  337. package/scripts/subagent-profiles.js +79 -0
  338. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  339. package/scripts/sync-github-about.js +52 -0
  340. package/scripts/sync-version.js +447 -0
  341. package/scripts/synthetic-dpo.js +234 -0
  342. package/scripts/telemetry-analytics.js +821 -0
  343. package/scripts/tessl-export.js +371 -0
  344. package/scripts/test-coverage.js +120 -0
  345. package/scripts/thompson-sampling.js +417 -0
  346. package/scripts/thumbgate-search.js +189 -0
  347. package/scripts/tool-kpi-tracker.js +12 -0
  348. package/scripts/tool-registry.js +811 -0
  349. package/scripts/train_from_feedback.py +933 -0
  350. package/scripts/user-profile.js +78 -0
  351. package/scripts/validate-feedback.js +581 -0
  352. package/scripts/validate-workflow-contract.js +287 -0
  353. package/scripts/vector-store.js +197 -0
  354. package/scripts/verification-loop.js +291 -0
  355. package/scripts/verify-obsidian-setup.sh +269 -0
  356. package/scripts/verify-run.js +269 -0
  357. package/scripts/webhook-delivery.js +62 -0
  358. package/scripts/weekly-auto-post.js +124 -0
  359. package/scripts/workflow-runs.js +154 -0
  360. package/scripts/workflow-sprint-intake.js +475 -0
  361. package/scripts/workspace-evolver.js +374 -0
  362. package/scripts/x-autonomous-marketing.js +139 -0
  363. package/scripts/xmemory-lite.js +405 -0
  364. package/skills/agent-memory/SKILL.md +97 -0
  365. package/skills/rlhf-feedback/SKILL.md +49 -0
  366. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  367. package/skills/solve-architecture-autonomy/tool.js +33 -0
  368. package/skills/thumbgate/SKILL.md +114 -0
  369. package/src/api/server.js +4206 -0
@@ -0,0 +1,650 @@
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" data-api="/api/event" src="/js/analytics.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
+ /* TABS */
46
+ .tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
47
+ .tab { padding: 12px 20px; font-size: 14px; font-weight: 500; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
48
+ .tab:hover { color: var(--text); }
49
+ .tab.active { color: var(--cyan); border-bottom-color: var(--cyan); }
50
+ .tab-content { display: none; }
51
+ .tab-content.active { display: block; }
52
+
53
+ /* SEARCH */
54
+ .search-bar { display: flex; gap: 12px; margin-bottom: 16px; }
55
+ .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; }
56
+ .search-input:focus { outline: none; border-color: var(--cyan); }
57
+ .search-input::placeholder { color: var(--text-muted); }
58
+ .btn { background: var(--cyan); color: var(--bg); padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; font-size: 13px; cursor: pointer; }
59
+ .btn:hover { opacity: 0.85; }
60
+ .filter-row { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
61
+ .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; }
62
+ .filter-btn:hover, .filter-btn.active { border-color: var(--cyan); color: var(--cyan); }
63
+
64
+ /* RULE CARDS */
65
+ .rules-list { display: flex; flex-direction: column; gap: 12px; }
66
+ .rule-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 20px; transition: border-color 0.15s; }
67
+ .rule-card:hover { border-color: rgba(34,211,238,0.3); }
68
+ .rule-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; gap: 12px; }
69
+ .rule-title { font-size: 15px; font-weight: 600; }
70
+ .rule-severity { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.05em; }
71
+ .rule-severity.critical { background: rgba(248,113,113,0.15); color: var(--red); }
72
+ .rule-severity.warning { background: rgba(251,191,36,0.15); color: var(--yellow); }
73
+ .rule-severity.info { background: var(--cyan-dim); color: var(--cyan); }
74
+ .rule-context { font-size: 13px; color: var(--text-muted); line-height: 1.5; margin-bottom: 10px; }
75
+ .rule-meta { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; }
76
+ .rule-meta-item { font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 4px; }
77
+ .rule-meta-item .value { color: var(--text); font-weight: 600; }
78
+ .rule-meta-item .value.green { color: var(--green); }
79
+ .rule-effectiveness { font-size: 12px; font-weight: 600; color: var(--green); background: rgba(74,222,128,0.1); padding: 2px 8px; border-radius: 4px; }
80
+ .rule-tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
81
+ .tag { font-size: 11px; background: var(--cyan-dim); color: var(--cyan); padding: 2px 8px; border-radius: 4px; }
82
+
83
+ /* TIMELINE */
84
+ .timeline { position: relative; padding-left: 24px; }
85
+ .timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0; width: 2px; background: var(--border); }
86
+ .timeline-item { position: relative; margin-bottom: 20px; }
87
+ .timeline-dot { position: absolute; left: -20px; top: 6px; width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--bg); }
88
+ .timeline-dot.up { background: var(--green); }
89
+ .timeline-dot.down { background: var(--red); }
90
+ .timeline-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
91
+ .timeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
92
+ .timeline-signal { font-size: 13px; font-weight: 600; }
93
+ .timeline-signal.up { color: var(--green); }
94
+ .timeline-signal.down { color: var(--red); }
95
+ .timeline-date { font-size: 12px; color: var(--text-muted); }
96
+ .timeline-context { font-size: 13px; color: var(--text-muted); }
97
+ .timeline-learned { font-size: 12px; color: var(--cyan); margin-top: 6px; }
98
+
99
+ /* INSIGHTS */
100
+ .insight-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 24px; margin-bottom: 16px; }
101
+ .insight-card h3 { font-size: 15px; margin-bottom: 12px; }
102
+ .insight-card .insight-list { list-style: none; }
103
+ .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; }
104
+ .insight-card .insight-list li:last-child { border-bottom: none; }
105
+ .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; }
106
+ .pro-badge h3 { color: var(--cyan); margin-bottom: 8px; }
107
+ .pro-badge p { font-size: 13px; color: var(--text-muted); margin-bottom: 16px; }
108
+ .pro-badge a { color: var(--bg); background: var(--cyan); padding: 10px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 14px; }
109
+ .pro-badge a:hover { opacity: 0.85; }
110
+
111
+ .empty { text-align: center; padding: 48px; color: var(--text-muted); font-size: 15px; }
112
+ .loading { text-align: center; padding: 24px; color: var(--text-muted); }
113
+ h2 { font-size: 20px; font-weight: 700; margin-bottom: 16px; letter-spacing: -0.02em; }
114
+
115
+ @media (max-width: 700px) {
116
+ .stats-grid { grid-template-columns: repeat(2, 1fr); }
117
+ .filter-row { flex-wrap: wrap; }
118
+ .rule-meta { flex-direction: column; gap: 8px; }
119
+ }
120
+
121
+ /* Feedback widget */
122
+ .feedback-widget { position: fixed; bottom: 20px; right: 20px; z-index: 1000; }
123
+ .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; }
124
+ .feedback-toggle:hover { transform: scale(1.05); }
125
+ .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); }
126
+ .feedback-panel.open { display: block; }
127
+ .feedback-panel h3 { font-size: 15px; margin-bottom: 12px; }
128
+ .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; }
129
+ .feedback-panel textarea:focus { outline: none; border-color: var(--cyan); }
130
+ .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; }
131
+ .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; }
132
+ .feedback-submit:hover { opacity: 0.85; }
133
+ .feedback-submit:disabled { opacity: 0.5; cursor: not-allowed; }
134
+ .feedback-success { color: var(--green); font-size: 13px; text-align: center; padding: 12px 0; }
135
+
136
+ </style>
137
+ </head>
138
+ <body>
139
+
140
+ <nav>
141
+ <div class="container">
142
+ <a href="/dashboard" class="nav-logo">👍👎 ThumbGate</a>
143
+ <div class="nav-links">
144
+ <a href="/dashboard">Dashboard</a>
145
+ <a href="/lessons" class="active">Lessons</a>
146
+ <a href="/">Landing Page</a>
147
+ <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
148
+ </div>
149
+ </div>
150
+ </nav>
151
+
152
+ <div class="container">
153
+ <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;">
154
+ <h1 style="font-size:22px;font-weight:700;margin-bottom:8px;letter-spacing:-0.02em;">📚 Lessons Learned</h1>
155
+ <p style="font-size:14px;color:var(--text-muted);line-height:1.6;max-width:700px;">Is your AI getting smarter? See every rule auto-generated from your feedback, how many mistakes each one prevented, and which patterns keep recurring. <span style="color:var(--purple);font-weight:600;">This is the proof your feedback works.</span></p>
156
+ <div style="display:flex;gap:16px;margin-top:12px;font-size:12px;color:var(--text-muted);">
157
+ <span>📋 <strong style="color:var(--text);">Active Rules</strong> — what was learned</span>
158
+ <span>📊 <strong style="color:var(--text);">Timeline</strong> — when it was learned</span>
159
+ <span>💡 <strong style="color:var(--text);">Insights</strong> — what to do next</span>
160
+ </div>
161
+ </div>
162
+ <div class="stats-grid">
163
+ <div class="stat-card" onclick="switchTab('rules'); filterSeverity('all')">
164
+ <div class="stat-label">Active Rules</div>
165
+ <div class="stat-value cyan" id="statRules">0</div>
166
+ <div class="stat-sub">Auto-generated from feedback</div>
167
+ </div>
168
+ <div class="stat-card" onclick="switchTab('rules'); filterSeverity('critical')">
169
+ <div class="stat-label">Critical</div>
170
+ <div class="stat-value red" id="statCritical">0</div>
171
+ <div class="stat-sub">Highest severity rules</div>
172
+ </div>
173
+ <div class="stat-card" onclick="switchTab('timeline')">
174
+ <div class="stat-label">Mistakes Prevented</div>
175
+ <div class="stat-value green" id="statPrevented">0</div>
176
+ <div class="stat-sub">Gate blocks from learned rules</div>
177
+ </div>
178
+ <div class="stat-card" onclick="switchTab('insights')">
179
+ <div class="stat-label">Approval Trend</div>
180
+ <div class="stat-value purple" id="statTrend">--</div>
181
+ <div class="stat-sub" id="statTrendSub">7-day rolling</div>
182
+ </div>
183
+ </div>
184
+
185
+ <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);">
186
+ Loading lessons view...
187
+ </div>
188
+
189
+ <div class="tabs">
190
+ <div class="tab active" onclick="switchTab('rules')">📋 Active Rules</div>
191
+ <div class="tab" onclick="switchTab('timeline')">📊 Feedback Timeline</div>
192
+ <div class="tab" onclick="switchTab('insights')">💡 Insights <span style="font-size:10px;color:var(--purple);font-weight:700;">PRO</span></div>
193
+ </div>
194
+
195
+ <!-- TAB 1: ACTIVE RULES -->
196
+ <div class="tab-content active" id="tab-rules">
197
+ <div class="search-bar">
198
+ <input type="text" class="search-input" id="ruleSearch" placeholder="Search rules... (try: git, push, test, env)" onkeydown="if(event.key==='Enter')searchRules()">
199
+ <button class="btn" onclick="searchRules()">Search</button>
200
+ </div>
201
+ <div class="filter-row">
202
+ <button class="filter-btn active" onclick="filterSeverity('all', this)">All</button>
203
+ <button class="filter-btn" onclick="filterSeverity('critical', this)">Critical</button>
204
+ <button class="filter-btn" onclick="filterSeverity('warning', this)">Warning</button>
205
+ <button class="filter-btn" onclick="filterSeverity('info', this)">Info</button>
206
+ </div>
207
+ <div class="rules-list" id="rulesList">
208
+ <div class="loading">Loading rules...</div>
209
+ </div>
210
+ </div>
211
+
212
+ <!-- TAB 2: FEEDBACK TIMELINE -->
213
+ <div class="tab-content" id="tab-timeline">
214
+ <div class="filter-row">
215
+ <button class="filter-btn active" onclick="filterTimeline('all', this)">All</button>
216
+ <button class="filter-btn" onclick="filterTimeline('up', this)">👍 Positive</button>
217
+ <button class="filter-btn" onclick="filterTimeline('down', this)">👎 Negative</button>
218
+ </div>
219
+ <div class="timeline" id="timelineList">
220
+ <div class="loading">Loading timeline...</div>
221
+ </div>
222
+ </div>
223
+
224
+ <!-- TAB 3: INSIGHTS (PRO) -->
225
+ <div class="tab-content" id="tab-insights">
226
+ <div class="insight-card">
227
+ <h3>🔥 Top Recurring Mistakes This Week</h3>
228
+ <ul class="insight-list" id="insightMistakes">
229
+ <li><span>Loading...</span></li>
230
+ </ul>
231
+ </div>
232
+ <div class="insight-card">
233
+ <h3>🛡️ Most Effective Rules</h3>
234
+ <ul class="insight-list" id="insightEffective">
235
+ <li><span>Loading...</span></li>
236
+ </ul>
237
+ </div>
238
+ <div class="insight-card">
239
+ <h3>🗑️ Stale Rules (no triggers in 30 days)</h3>
240
+ <ul class="insight-list" id="insightStale">
241
+ <li><span>Loading...</span></li>
242
+ </ul>
243
+ </div>
244
+ <div class="pro-badge" id="proBadge" style="display:none;">
245
+ <h3>Unlock Full Insights</h3>
246
+ <p>DPO training data export, effectiveness charts, pattern clustering, and weekly auto-digests.</p>
247
+ <a href="/#pricing">Get Pro — $19/mo</a>
248
+ </div>
249
+ </div>
250
+ </div>
251
+
252
+ <script>
253
+ var allRules = [];
254
+ var allTimeline = [];
255
+ var isDemo = true;
256
+ var API_KEY = '';
257
+ var dashboardSnapshot = null;
258
+ const BOOTSTRAP_API_KEY = __LESSONS_BOOTSTRAP_KEY__;
259
+ const LOCAL_PRO_BOOTSTRAP = __LESSONS_BOOTSTRAP_ENABLED__;
260
+
261
+ function escHtml(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
262
+
263
+ function getHeaders() {
264
+ return { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' };
265
+ }
266
+
267
+ function hasBootstrapKey() {
268
+ return LOCAL_PRO_BOOTSTRAP && Boolean(BOOTSTRAP_API_KEY);
269
+ }
270
+
271
+ function setMode(message, demoMode) {
272
+ isDemo = demoMode;
273
+ var el = document.getElementById('lessonsMode');
274
+ if (!el) return;
275
+ el.textContent = message;
276
+ el.style.borderColor = demoMode ? 'var(--border)' : 'rgba(74,222,128,0.25)';
277
+ el.style.color = demoMode ? 'var(--text-muted)' : 'var(--green)';
278
+ }
279
+
280
+ function formatDate(value) {
281
+ if (!value) return '';
282
+ var ts = new Date(value);
283
+ if (Number.isNaN(ts.getTime())) return String(value);
284
+ return ts.toLocaleString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });
285
+ }
286
+
287
+ function highlightCard(index) {
288
+ document.querySelectorAll('.stat-card').forEach(function(c, i) {
289
+ c.classList.toggle('selected', i === index);
290
+ });
291
+ }
292
+ function switchTab(name) {
293
+ // Highlight the correct tab header (only inside .tabs, not stat cards)
294
+ document.querySelectorAll('.tabs .tab').forEach(function(t) { t.classList.remove('active'); });
295
+ document.querySelectorAll('.tab-content').forEach(function(c) { c.classList.remove('active'); });
296
+ var tabHeaders = document.querySelectorAll('.tabs .tab');
297
+ var tabMap = { rules: 0, timeline: 1, insights: 2 };
298
+ if (tabHeaders[tabMap[name]]) tabHeaders[tabMap[name]].classList.add('active');
299
+ var content = document.getElementById('tab-' + name);
300
+ if (content) content.classList.add('active');
301
+ // Highlight the corresponding stat card
302
+ var cardMap = { rules: 0, timeline: 2, insights: 3 };
303
+ highlightCard(cardMap[name] !== undefined ? cardMap[name] : -1);
304
+ if (typeof plausible === 'function') plausible('lessons_tab', { props: { tab: name } });
305
+ }
306
+
307
+ function filterSeverity(level, el) {
308
+ // Update filter button active state
309
+ var filterBtns = document.querySelectorAll('#tab-rules .filter-btn');
310
+ filterBtns.forEach(function(b) { b.classList.remove('active'); });
311
+ if (el) {
312
+ el.classList.add('active');
313
+ } else {
314
+ // Find the matching button and activate it
315
+ filterBtns.forEach(function(b) {
316
+ if (b.textContent.trim().toLowerCase() === level || (level === 'all' && b.textContent.trim() === 'All')) {
317
+ b.classList.add('active');
318
+ }
319
+ });
320
+ }
321
+ // Highlight Critical card (index 1) when filtering critical, else Active Rules card (index 0)
322
+ if (level === 'critical') { highlightCard(1); } else { highlightCard(0); }
323
+ var filtered = level === 'all' ? allRules : allRules.filter(function(r) { return r.severity === level; });
324
+ renderRules(filtered);
325
+ }
326
+
327
+ function filterTimeline(signal, el) {
328
+ if (el) {
329
+ document.querySelectorAll('#tab-timeline .filter-btn').forEach(function(b) { b.classList.remove('active'); });
330
+ el.classList.add('active');
331
+ }
332
+ var filtered = signal === 'all' ? allTimeline : allTimeline.filter(function(r) { return r.signal === signal; });
333
+ renderTimeline(filtered);
334
+ }
335
+
336
+ function searchRules() {
337
+ var q = document.getElementById('ruleSearch').value.toLowerCase().trim();
338
+ if (!q) { renderRules(allRules); return; }
339
+ var filtered = allRules.filter(function(r) {
340
+ return (r.title || '').toLowerCase().includes(q) ||
341
+ (r.context || '').toLowerCase().includes(q) ||
342
+ (r.tags || []).some(function(t) { return t.toLowerCase().includes(q); });
343
+ });
344
+ renderRules(filtered);
345
+ }
346
+
347
+ function getSeverity(rule) {
348
+ if (rule.occurrences >= 10 || (rule.tags || []).includes('CRITICAL')) return 'critical';
349
+ if (rule.occurrences >= 3) return 'warning';
350
+ return 'info';
351
+ }
352
+
353
+ function renderRules(rules) {
354
+ var el = document.getElementById('rulesList');
355
+ if (!rules.length) { el.innerHTML = '<div class="empty">No rules match your filter</div>'; return; }
356
+ el.innerHTML = rules.map(function(r) {
357
+ var sev = r.severity || getSeverity(r);
358
+ return '<div class="rule-card" data-rule-id="' + escHtml(r.id || '') + '">' +
359
+ '<div class="rule-header">' +
360
+ '<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>' +
361
+ '<span class="rule-severity ' + sev + '">' + sev + '</span>' +
362
+ '</div>' +
363
+ (r.context ? '<div class="rule-context">' + escHtml(r.context) + '</div>' : '') +
364
+ '<div class="rule-meta">' +
365
+ '<div class="rule-meta-item">Triggered <span class="value">' + (r.occurrences || 0) + '</span> times</div>' +
366
+ (r.lastTriggered ? '<div class="rule-meta-item">Last: <span class="value">' + escHtml(r.lastTriggered) + '</span></div>' : '') +
367
+ '</div>' +
368
+ (r.tags && r.tags.length ? '<div class="rule-tags">' + r.tags.map(function(t) { return '<span class="tag">' + escHtml(t) + '</span>'; }).join('') + '</div>' : '') +
369
+ '</div>';
370
+ }).join('');
371
+ }
372
+
373
+ function renderTimeline(items) {
374
+ var el = document.getElementById('timelineList');
375
+ if (!items.length) { el.innerHTML = '<div class="empty">No feedback events</div>'; return; }
376
+ el.innerHTML = items.map(function(item) {
377
+ var sig = item.signal || 'down';
378
+ var emoji = sig === 'up' ? '👍' : '👎';
379
+ var fbId = item.feedbackId || item.id || '';
380
+ return '<div class="timeline-item" data-feedback-id="' + escHtml(fbId) + '">' +
381
+ '<div class="timeline-dot ' + sig + '"></div>' +
382
+ '<div class="timeline-card">' +
383
+ '<div class="timeline-header">' +
384
+ '<span class="timeline-signal ' + sig + '">' + emoji + ' ' + (sig === 'up' ? 'Positive' : 'Negative') + '</span>' +
385
+ '<span class="timeline-date">' + escHtml(item.date || '') + '</span>' +
386
+ '</div>' +
387
+ '<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>' +
388
+ (item.learned ? '<div class="timeline-learned">→ Rule created: ' + escHtml(item.learned) + '</div>' : '') +
389
+ '</div>' +
390
+ '</div>';
391
+ }).join('');
392
+ }
393
+
394
+ function renderInsights(data) {
395
+ var mistakes = document.getElementById('insightMistakes');
396
+ var effective = document.getElementById('insightEffective');
397
+ var stale = document.getElementById('insightStale');
398
+
399
+ // Dedup by title — keep the one with highest occurrences
400
+ var seen = {};
401
+ var dedupedRules = allRules.filter(function(r) {
402
+ var key = (r.title || '').trim().toLowerCase();
403
+ if (!key) return false;
404
+ if (seen[key] && seen[key].occurrences >= (r.occurrences || 0)) return false;
405
+ seen[key] = r;
406
+ return true;
407
+ });
408
+
409
+ var topMistakes = dedupedRules.filter(function(r) { return r.severity === 'critical'; })
410
+ .sort(function(a, b) { return (b.occurrences || 0) - (a.occurrences || 0); }).slice(0, 5);
411
+ var topEffective = dedupedRules.slice().sort(function(a, b) { return (b.occurrences || 0) - (a.occurrences || 0); })
412
+ .filter(function(r) { return (r.occurrences || 0) > 1; }).slice(0, 5);
413
+ var staleRules = dedupedRules.filter(function(r) { return !r.lastTriggered || r.lastTriggered === 'never'; });
414
+
415
+ function insightRow(r, badge) {
416
+ var title = r.title.length > 80 ? r.title.slice(0, 77) + '...' : r.title;
417
+ return '<li style="cursor:pointer" onclick="switchTab(\'rules\'); searchAndHighlight(\'' + escHtml(r.id) + '\')">'
418
+ + '<span title="' + escHtml(r.title) + '">' + escHtml(title) + '</span>' + badge + '</li>';
419
+ }
420
+
421
+ mistakes.innerHTML = topMistakes.length ? topMistakes.map(function(r) {
422
+ return insightRow(r, '<span style="color:var(--red);font-weight:600">' + (r.occurrences || 0) + 'x</span>');
423
+ }).join('') : '<li><span style="color:var(--green)">No critical mistakes this week</span></li>';
424
+
425
+ effective.innerHTML = topEffective.length ? topEffective.map(function(r) {
426
+ return insightRow(r, '<span class="rule-effectiveness">Triggered ' + (r.occurrences || 0) + 'x</span>');
427
+ }).join('') : '<li><span>No high-frequency rules yet</span></li>';
428
+
429
+ stale.innerHTML = staleRules.length ? staleRules.map(function(r) {
430
+ return insightRow(r, '<span style="color:var(--text-muted)">Archive?</span>');
431
+ }).join('') : '<li><span style="color:var(--green)">All rules are active</span></li>';
432
+ }
433
+
434
+ function searchAndHighlight(ruleId) {
435
+ var el = document.querySelector('[data-rule-id="' + ruleId + '"]');
436
+ if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.style.borderColor = 'var(--cyan)'; setTimeout(function() { el.style.borderColor = ''; }, 2000); }
437
+ }
438
+
439
+ function deriveSeverity(lesson) {
440
+ var severity = String(lesson.importance || '').toLowerCase();
441
+ if (severity === 'critical' || severity === 'high') return 'critical';
442
+ if (severity === 'medium') return 'warning';
443
+ return 'info';
444
+ }
445
+
446
+ function mapLiveRules(payload) {
447
+ return (payload.results || []).map(function(result) {
448
+ var linkedGate = (((result.systemResponse || {}).linkedAutoGates) || [])[0] || null;
449
+ var sourceFeedback = ((result.systemResponse || {}).sourceFeedback) || null;
450
+ return {
451
+ id: result.id,
452
+ title: result.title || ((result.lesson || {}).summary) || 'Lesson',
453
+ context: ((result.lesson || {}).howToAvoid) || ((result.lesson || {}).summary) || (sourceFeedback && sourceFeedback.context) || '',
454
+ occurrences: linkedGate && linkedGate.occurrences ? linkedGate.occurrences : 1,
455
+ prevented: linkedGate && linkedGate.occurrences ? linkedGate.occurrences : 0,
456
+ severity: deriveSeverity(result),
457
+ tags: Array.isArray(result.tags) ? result.tags : [],
458
+ lastTriggered: sourceFeedback && sourceFeedback.timestamp ? formatDate(sourceFeedback.timestamp) : formatDate(result.timestamp),
459
+ sourceFeedbackId: sourceFeedback && sourceFeedback.id ? sourceFeedback.id : result.sourceFeedbackId
460
+ };
461
+ });
462
+ }
463
+
464
+ function mapTimelineItems(payload, lessonMap) {
465
+ return (payload.results || []).map(function(entry) {
466
+ var linkedLesson = lessonMap.get(entry.id) || null;
467
+ return {
468
+ signal: entry.signal || 'down',
469
+ context: entry.context || entry.title || '',
470
+ date: formatDate(entry.timestamp),
471
+ learned: linkedLesson ? linkedLesson.title : '',
472
+ feedbackId: entry.id || ''
473
+ };
474
+ });
475
+ }
476
+
477
+ // Demo data
478
+ var demoRules = [
479
+ { 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' },
480
+ { 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' },
481
+ { 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' },
482
+ { 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' },
483
+ { 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' },
484
+ { 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' },
485
+ { 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' },
486
+ { 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' },
487
+ ];
488
+
489
+ var demoTimeline = [
490
+ { signal: 'down', context: 'Claimed fix done without pushing code', date: '2 hours ago', learned: 'Push before claiming done' },
491
+ { signal: 'down', context: 'Said endpoint not deployed without checking', date: '3 hours ago', learned: 'Never claim without evidence' },
492
+ { signal: 'up', context: 'Clean 4-tier pricing implementation with tests', date: '5 hours ago' },
493
+ { signal: 'down', context: 'Made false claims about what was broken', date: '6 hours ago', learned: 'Never claim without evidence' },
494
+ { signal: 'up', context: 'Statusline reorder done correctly first try', date: '8 hours ago' },
495
+ { signal: 'down', context: 'Delegated to subagent without verifying output', date: 'yesterday', learned: 'Execute, don\'t ask permission' },
496
+ { signal: 'up', context: 'Evidence-based PR management with iterative fixes', date: 'yesterday' },
497
+ { signal: 'down', context: 'Spent 45min on code when curl would have diagnosed in 30s', date: '3 days ago', learned: 'curl first, never edit .env' },
498
+ ];
499
+
500
+ function renderUpgradeWall(containerId) {
501
+ var el = document.getElementById(containerId);
502
+ if (!el) return;
503
+ var wall = document.createElement('div');
504
+ wall.style.cssText = 'position:relative;margin-top:8px;';
505
+ 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;">' +
506
+ '<div style="text-align:center;background:rgba(10,10,15,0.92);border:1px solid #333;border-radius:12px;padding:28px 36px;">' +
507
+ '<div style="font-size:20px;font-weight:700;color:#fff;margin-bottom:8px;">Unlock your full lessons</div>' +
508
+ '<div style="color:#aaa;margin-bottom:16px;">Pro shows your real prevention rules, timeline, and insights.</div>' +
509
+ '<a href="https://buy.stripe.com/5kQ4gzbmI9Lo6tPayn3sI06" target="_blank" rel="noopener" ' +
510
+ '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>' +
511
+ '<div style="color:#666;font-size:12px;margin-top:10px;">npx thumbgate pro --activate --key=YOUR_KEY</div>' +
512
+ '</div></div>';
513
+ el.appendChild(wall);
514
+ }
515
+
516
+ function loadDemo() {
517
+ allRules = demoRules.slice(0, 3);
518
+ allTimeline = demoTimeline.slice(0, 3);
519
+ dashboardSnapshot = null;
520
+
521
+ document.getElementById('statRules').textContent = demoRules.length;
522
+ document.getElementById('statCritical').textContent = demoRules.filter(function(r) { return r.severity === 'critical'; }).length;
523
+ document.getElementById('statPrevented').textContent = demoRules.reduce(function(sum, r) { return sum + (r.prevented || 0); }, 0);
524
+ document.getElementById('statTrend').textContent = '12.5%';
525
+ document.getElementById('statTrendSub').textContent = '7-day approval rate';
526
+
527
+ renderRules(allRules);
528
+ renderTimeline(allTimeline);
529
+ renderInsights();
530
+ renderUpgradeWall('rulesList');
531
+ renderUpgradeWall('timelineList');
532
+ setMode('Demo preview — upgrade to Pro to see your live lessons, timeline, and insights.', true);
533
+ document.getElementById('proBadge').style.display = 'block';
534
+ }
535
+
536
+ async function loadLive() {
537
+ if (!hasBootstrapKey()) {
538
+ loadDemo();
539
+ return;
540
+ }
541
+ API_KEY = String(BOOTSTRAP_API_KEY || '').trim();
542
+ try {
543
+ const responses = await Promise.all([
544
+ fetch('/v1/feedback/stats', { headers: getHeaders() }),
545
+ fetch('/v1/lessons/search?limit=50', { headers: getHeaders() }),
546
+ fetch('/v1/search?q=*&limit=40&source=feedback', { headers: getHeaders() }),
547
+ fetch('/v1/dashboard', { headers: getHeaders() })
548
+ ]);
549
+ if (responses.some(function(res) { return !res.ok; })) {
550
+ throw new Error('Local Pro bootstrap unavailable');
551
+ }
552
+
553
+ const stats = await responses[0].json();
554
+ const lessonsPayload = await responses[1].json();
555
+ const feedbackPayload = await responses[2].json();
556
+ dashboardSnapshot = await responses[3].json();
557
+
558
+ allRules = mapLiveRules(lessonsPayload);
559
+ var lessonMap = new Map(allRules
560
+ .filter(function(rule) { return rule.sourceFeedbackId; })
561
+ .map(function(rule) { return [rule.sourceFeedbackId, rule]; }));
562
+ allTimeline = mapTimelineItems(feedbackPayload, lessonMap);
563
+
564
+ document.getElementById('statRules').textContent = allRules.length;
565
+ document.getElementById('statCritical').textContent = allRules.filter(function(r) { return r.severity === 'critical'; }).length;
566
+ document.getElementById('statPrevented').textContent = allRules.reduce(function(sum, r) { return sum + (r.prevented || 0); }, 0);
567
+ document.getElementById('statTrend').textContent = ((stats.recentRate || stats.approvalRate || 0) * 100).toFixed(1) + '%';
568
+ document.getElementById('statTrendSub').textContent = 'Live approval rate';
569
+
570
+ renderRules(allRules);
571
+ renderTimeline(allTimeline);
572
+ renderInsights(dashboardSnapshot);
573
+ document.getElementById('proBadge').style.display = 'none';
574
+ setMode('Local Pro connected — this lessons view is reading your live rules, feedback timeline, and insights.', false);
575
+ } catch (_err) {
576
+ loadDemo();
577
+ }
578
+ }
579
+
580
+ loadLive().then(function() {
581
+ // Default: highlight Active Rules card on page load
582
+ highlightCard(0);
583
+
584
+ // Handle #feedbackId hash — scroll to and highlight the matching item
585
+ var hash = window.location.hash.replace('#', '');
586
+ if (!hash) return;
587
+ // Try rule cards first
588
+ var el = document.querySelector('[data-rule-id="' + hash + '"]') ||
589
+ document.querySelector('[data-feedback-id="' + hash + '"]');
590
+ if (!el) {
591
+ // Search in timeline by switching tab
592
+ switchTab('timeline');
593
+ el = document.querySelector('[data-feedback-id="' + hash + '"]');
594
+ }
595
+ if (el) {
596
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
597
+ el.style.borderColor = 'var(--cyan)';
598
+ el.style.boxShadow = '0 0 12px rgba(34,211,238,0.3)';
599
+ setTimeout(function() { el.style.borderColor = ''; el.style.boxShadow = ''; }, 4000);
600
+ }
601
+ });
602
+ </script>
603
+
604
+ <div class="feedback-widget">
605
+ <div class="feedback-panel" id="feedbackPanel">
606
+ <h3>Share feedback with ThumbGate team</h3>
607
+ <select id="feedbackCategory">
608
+ <option value="bug">Bug report</option>
609
+ <option value="feature">Feature request</option>
610
+ <option value="question">Question</option>
611
+ </select>
612
+ <textarea id="feedbackText" placeholder="What's on your mind? Be honest — we read every one."></textarea>
613
+ <button class="feedback-submit" id="feedbackSubmit" onclick="submitFeedback()">Submit</button>
614
+ <div id="feedbackResult"></div>
615
+ </div>
616
+ <button class="feedback-toggle" onclick="toggleFeedback()">💬 Feedback</button>
617
+ </div>
618
+ <script>
619
+ function toggleFeedback() {
620
+ document.getElementById('feedbackPanel').classList.toggle('open');
621
+ document.getElementById('feedbackResult').innerHTML = '';
622
+ }
623
+ async function submitFeedback() {
624
+ var btn = document.getElementById('feedbackSubmit');
625
+ var text = document.getElementById('feedbackText').value.trim();
626
+ var category = document.getElementById('feedbackCategory').value;
627
+ if (!text) return;
628
+ btn.disabled = true; btn.textContent = 'Submitting...';
629
+ try {
630
+ var res = await fetch('/api/feedback/submit', {
631
+ method: 'POST',
632
+ headers: { 'Content-Type': 'application/json' },
633
+ body: JSON.stringify({ category: category, message: text })
634
+ });
635
+ var data = await res.json();
636
+ if (data.success) {
637
+ 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>';
638
+ document.getElementById('feedbackText').value = '';
639
+ } else {
640
+ document.getElementById('feedbackResult').innerHTML = '<div style="color:var(--red);font-size:13px">Failed: ' + (data.error || 'unknown') + '</div>';
641
+ }
642
+ } catch (e) {
643
+ document.getElementById('feedbackResult').innerHTML = '<div style="color:var(--red);font-size:13px">Network error. Try again.</div>';
644
+ }
645
+ btn.disabled = false; btn.textContent = 'Submit';
646
+ }
647
+ </script>
648
+
649
+ </body>
650
+ </html>