claude-dev-env 1.38.1 → 1.40.0

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 (282) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,453 @@
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>Debounced search — three approaches</title>
7
+ <style>
8
+ :root {
9
+ --ivory: #FAF9F5;
10
+ --slate: #141413;
11
+ --clay: #D97757;
12
+ --oat: #E3DACC;
13
+ --olive: #788C5D;
14
+ --gray-150: #F0EEE6;
15
+ --gray-300: #D1CFC5;
16
+ --gray-500: #87867F;
17
+ --gray-700: #3D3D3A;
18
+ --white: #FFFFFF;
19
+
20
+ --serif: ui-serif, Georgia, 'Times New Roman', serif;
21
+ --sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
22
+ --mono: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
23
+ }
24
+
25
+ * { margin: 0; padding: 0; box-sizing: border-box; }
26
+
27
+ body {
28
+ font-family: var(--sans);
29
+ background: var(--ivory);
30
+ color: var(--gray-700);
31
+ line-height: 1.55;
32
+ padding: 56px 32px 96px;
33
+ -webkit-font-smoothing: antialiased;
34
+ }
35
+
36
+ .page {
37
+ max-width: 1360px;
38
+ margin: 0 auto;
39
+ }
40
+
41
+ /* ---------- header ---------- */
42
+
43
+ header.page-head {
44
+ margin-bottom: 48px;
45
+ max-width: 760px;
46
+ }
47
+
48
+ .eyebrow {
49
+ font-size: 12px;
50
+ letter-spacing: 0.08em;
51
+ text-transform: uppercase;
52
+ color: var(--gray-500);
53
+ margin-bottom: 12px;
54
+ }
55
+
56
+ h1 {
57
+ font-family: var(--serif);
58
+ font-weight: 500;
59
+ font-size: 38px;
60
+ line-height: 1.15;
61
+ color: var(--slate);
62
+ margin-bottom: 18px;
63
+ letter-spacing: -0.01em;
64
+ }
65
+
66
+ .prompt-box {
67
+ background: var(--gray-150);
68
+ border: 1.5px solid var(--gray-300);
69
+ border-radius: 12px;
70
+ padding: 16px 20px;
71
+ font-size: 14.5px;
72
+ color: var(--gray-700);
73
+ }
74
+
75
+ .prompt-box .label {
76
+ font-family: var(--mono);
77
+ font-size: 11px;
78
+ text-transform: uppercase;
79
+ letter-spacing: 0.06em;
80
+ color: var(--gray-500);
81
+ display: block;
82
+ margin-bottom: 6px;
83
+ }
84
+
85
+ /* ---------- approach grid ---------- */
86
+
87
+ .approaches {
88
+ display: grid;
89
+ grid-template-columns: repeat(3, minmax(0, 1fr));
90
+ gap: 28px;
91
+ margin-bottom: 56px;
92
+ }
93
+
94
+ @media (max-width: 1100px) {
95
+ .approaches { grid-template-columns: 1fr; }
96
+ }
97
+
98
+ .approach {
99
+ background: var(--white);
100
+ border: 1.5px solid var(--gray-300);
101
+ border-radius: 12px;
102
+ padding: 24px;
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 20px;
106
+ }
107
+
108
+ .approach-head h2 {
109
+ font-family: var(--serif);
110
+ font-weight: 500;
111
+ font-size: 21px;
112
+ color: var(--slate);
113
+ margin-bottom: 6px;
114
+ }
115
+
116
+ .approach-head .num {
117
+ display: inline-block;
118
+ font-family: var(--mono);
119
+ font-size: 12px;
120
+ background: var(--oat);
121
+ color: var(--slate);
122
+ padding: 2px 8px;
123
+ border-radius: 8px;
124
+ margin-right: 8px;
125
+ vertical-align: 3px;
126
+ }
127
+
128
+ .approach-head p {
129
+ font-size: 14px;
130
+ color: var(--gray-500);
131
+ }
132
+
133
+ /* ---------- code panel ---------- */
134
+
135
+ .code {
136
+ background: var(--slate);
137
+ border-radius: 12px;
138
+ padding: 18px 20px;
139
+ overflow-x: auto;
140
+ }
141
+
142
+ .code pre {
143
+ font-family: var(--mono);
144
+ font-size: 12.5px;
145
+ line-height: 1.65;
146
+ color: #E8E6DE;
147
+ white-space: pre;
148
+ }
149
+
150
+ .code .kw { color: var(--clay); } /* keywords */
151
+ .code .str { color: var(--olive); } /* strings */
152
+ .code .cm { color: var(--gray-500); } /* comments */
153
+ .code .fn { color: #C9B98A; } /* identifiers, subtle warm */
154
+
155
+ /* ---------- tradeoffs table ---------- */
156
+
157
+ .tradeoffs {
158
+ border: 1.5px solid var(--gray-300);
159
+ border-radius: 8px;
160
+ overflow: hidden;
161
+ font-size: 13px;
162
+ }
163
+
164
+ .tradeoffs .row {
165
+ display: grid;
166
+ grid-template-columns: 1fr 1fr;
167
+ }
168
+
169
+ .tradeoffs .row + .row {
170
+ border-top: 1.5px solid var(--gray-300);
171
+ }
172
+
173
+ .tradeoffs .cell {
174
+ padding: 10px 14px;
175
+ }
176
+
177
+ .tradeoffs .cell:first-child {
178
+ border-right: 1.5px solid var(--gray-300);
179
+ }
180
+
181
+ .tradeoffs .head {
182
+ background: var(--gray-150);
183
+ font-weight: 600;
184
+ color: var(--slate);
185
+ font-size: 12px;
186
+ text-transform: uppercase;
187
+ letter-spacing: 0.04em;
188
+ }
189
+
190
+ .tradeoffs .pro::before,
191
+ .tradeoffs .con::before {
192
+ content: '';
193
+ display: inline-block;
194
+ width: 6px;
195
+ height: 6px;
196
+ border-radius: 50%;
197
+ margin-right: 8px;
198
+ vertical-align: 2px;
199
+ }
200
+ .tradeoffs .pro::before { background: var(--olive); }
201
+ .tradeoffs .con::before { background: var(--clay); }
202
+
203
+ /* ---------- chip footer ---------- */
204
+
205
+ .chips {
206
+ display: flex;
207
+ flex-wrap: wrap;
208
+ gap: 8px;
209
+ }
210
+
211
+ .chip {
212
+ font-family: var(--mono);
213
+ font-size: 11.5px;
214
+ background: var(--gray-150);
215
+ border: 1.5px solid var(--gray-300);
216
+ color: var(--gray-700);
217
+ padding: 5px 10px;
218
+ border-radius: 8px;
219
+ white-space: nowrap;
220
+ }
221
+
222
+ .chip strong { color: var(--slate); font-weight: 600; }
223
+
224
+ /* ---------- recommendation ---------- */
225
+
226
+ .reco {
227
+ border-left: 4px solid var(--clay);
228
+ background: var(--white);
229
+ border-radius: 0 12px 12px 0;
230
+ padding: 24px 28px;
231
+ max-width: 860px;
232
+ }
233
+
234
+ .reco h2 {
235
+ font-family: var(--serif);
236
+ font-weight: 500;
237
+ font-size: 22px;
238
+ color: var(--slate);
239
+ margin-bottom: 10px;
240
+ }
241
+
242
+ .reco p {
243
+ font-size: 15px;
244
+ margin-bottom: 8px;
245
+ }
246
+
247
+ .reco code {
248
+ font-family: var(--mono);
249
+ font-size: 0.92em;
250
+ background: var(--gray-150);
251
+ padding: 1px 6px;
252
+ border-radius: 4px;
253
+ }
254
+ </style>
255
+ </head>
256
+ <body>
257
+ <div class="page">
258
+
259
+ <header class="page-head">
260
+ <div class="eyebrow">Exploration · Birchline web client</div>
261
+ <h1>Three ways to implement debounced search</h1>
262
+ <div class="prompt-box">
263
+ <span class="label">Prompt</span>
264
+ Show me three different ways to implement debounced search for the task
265
+ filter input in our React codebase, with tradeoffs for each.
266
+ </div>
267
+ </header>
268
+
269
+ <!-- ============================================================= -->
270
+
271
+ <section class="approaches">
272
+
273
+ <!-- ---------- Approach 1 ---------- -->
274
+ <article class="approach">
275
+ <header class="approach-head">
276
+ <h2><span class="num">01</span>Inline useEffect + setTimeout</h2>
277
+ <p>Debounce logic lives directly inside the component that owns the input.</p>
278
+ </header>
279
+
280
+ <div class="code"><pre><span class="kw">export function</span> <span class="fn">TaskSearch</span>() {
281
+ <span class="kw">const</span> [draft, setDraft] = <span class="fn">useState</span>(<span class="str">''</span>);
282
+ <span class="kw">const</span> [query, setQuery] = <span class="fn">useState</span>(<span class="str">''</span>);
283
+
284
+ <span class="fn">useEffect</span>(() <span class="kw">=&gt;</span> {
285
+ <span class="kw">const</span> id = <span class="fn">setTimeout</span>(() <span class="kw">=&gt;</span> setQuery(draft), <span class="str">300</span>);
286
+ <span class="kw">return</span> () <span class="kw">=&gt;</span> <span class="fn">clearTimeout</span>(id);
287
+ }, [draft]);
288
+
289
+ <span class="kw">const</span> { data } = <span class="fn">useTasks</span>({ search: query });
290
+
291
+ <span class="kw">return</span> (
292
+ &lt;<span class="fn">input</span>
293
+ value={draft}
294
+ onChange={(e) <span class="kw">=&gt;</span> setDraft(e.target.value)}
295
+ placeholder=<span class="str">"Filter tasks…"</span>
296
+ /&gt;
297
+ );
298
+ }</pre></div>
299
+
300
+ <div class="tradeoffs">
301
+ <div class="row head">
302
+ <div class="cell">Pro</div>
303
+ <div class="cell">Con</div>
304
+ </div>
305
+ <div class="row">
306
+ <div class="cell pro">Zero new abstractions to learn</div>
307
+ <div class="cell con">Logic duplicated everywhere search exists</div>
308
+ </div>
309
+ <div class="row">
310
+ <div class="cell pro">Easy to step through in devtools</div>
311
+ <div class="cell con">Two pieces of state for one conceptual value</div>
312
+ </div>
313
+ <div class="row">
314
+ <div class="cell pro">No dependency or bundle change</div>
315
+ <div class="cell con">Delay constant is buried in component body</div>
316
+ </div>
317
+ </div>
318
+
319
+ <div class="chips">
320
+ <span class="chip">Bundle impact: <strong>+0 kb</strong></span>
321
+ <span class="chip">Testability: <strong>medium</strong></span>
322
+ <span class="chip">Reuse: <strong>low</strong></span>
323
+ <span class="chip">SSR safe: <strong>yes</strong></span>
324
+ </div>
325
+ </article>
326
+
327
+ <!-- ---------- Approach 2 ---------- -->
328
+ <article class="approach">
329
+ <header class="approach-head">
330
+ <h2><span class="num">02</span>Custom useDebounce hook</h2>
331
+ <p>Extract the timer into a shared hook under <code style="font-family:var(--mono)">src/hooks/</code>.</p>
332
+ </header>
333
+
334
+ <div class="code"><pre><span class="cm">// src/hooks/useDebounce.ts</span>
335
+ <span class="kw">export function</span> <span class="fn">useDebounce</span>&lt;T&gt;(value: T, ms = <span class="str">300</span>): T {
336
+ <span class="kw">const</span> [debounced, setDebounced] = <span class="fn">useState</span>(value);
337
+ <span class="fn">useEffect</span>(() <span class="kw">=&gt;</span> {
338
+ <span class="kw">const</span> id = <span class="fn">setTimeout</span>(() <span class="kw">=&gt;</span> setDebounced(value), ms);
339
+ <span class="kw">return</span> () <span class="kw">=&gt;</span> <span class="fn">clearTimeout</span>(id);
340
+ }, [value, ms]);
341
+ <span class="kw">return</span> debounced;
342
+ }
343
+
344
+ <span class="cm">// TaskSearch.tsx</span>
345
+ <span class="kw">const</span> [draft, setDraft] = <span class="fn">useState</span>(<span class="str">''</span>);
346
+ <span class="kw">const</span> query = <span class="fn">useDebounce</span>(draft, <span class="str">300</span>);
347
+ <span class="kw">const</span> { data } = <span class="fn">useTasks</span>({ search: query });</pre></div>
348
+
349
+ <div class="tradeoffs">
350
+ <div class="row head">
351
+ <div class="cell">Pro</div>
352
+ <div class="cell">Con</div>
353
+ </div>
354
+ <div class="row">
355
+ <div class="cell pro">Single import reused across filter, command bar, board search</div>
356
+ <div class="cell con">One more file to maintain and document</div>
357
+ </div>
358
+ <div class="row">
359
+ <div class="cell pro">Trivial to unit test with fake timers</div>
360
+ <div class="cell con">Generic <code style="font-family:var(--mono)">T</code> hides intent slightly</div>
361
+ </div>
362
+ <div class="row">
363
+ <div class="cell pro">Delay is a visible, tunable argument</div>
364
+ <div class="cell con">Still re-renders on every keystroke</div>
365
+ </div>
366
+ </div>
367
+
368
+ <div class="chips">
369
+ <span class="chip">Bundle impact: <strong>+0.2 kb</strong></span>
370
+ <span class="chip">Testability: <strong>high</strong></span>
371
+ <span class="chip">Reuse: <strong>high</strong></span>
372
+ <span class="chip">SSR safe: <strong>yes</strong></span>
373
+ </div>
374
+ </article>
375
+
376
+ <!-- ---------- Approach 3 ---------- -->
377
+ <article class="approach">
378
+ <header class="approach-head">
379
+ <h2><span class="num">03</span>Tiny external library</h2>
380
+ <p>Adopt <code style="font-family:var(--mono)">use-debounce</code> for both values and callbacks.</p>
381
+ </header>
382
+
383
+ <div class="code"><pre><span class="kw">import</span> { useDebouncedCallback }
384
+ <span class="kw">from</span> <span class="str">'use-debounce'</span>;
385
+
386
+ <span class="kw">export function</span> <span class="fn">TaskSearch</span>() {
387
+ <span class="kw">const</span> [query, setQuery] = <span class="fn">useState</span>(<span class="str">''</span>);
388
+
389
+ <span class="kw">const</span> onChange = <span class="fn">useDebouncedCallback</span>(
390
+ (next: <span class="kw">string</span>) <span class="kw">=&gt;</span> setQuery(next),
391
+ <span class="str">300</span>,
392
+ { leading: <span class="kw">false</span>, maxWait: <span class="str">1000</span> },
393
+ );
394
+
395
+ <span class="kw">const</span> { data } = <span class="fn">useTasks</span>({ search: query });
396
+
397
+ <span class="kw">return</span> (
398
+ &lt;<span class="fn">input</span>
399
+ defaultValue=<span class="str">""</span>
400
+ onChange={(e) <span class="kw">=&gt;</span> onChange(e.target.value)}
401
+ /&gt;
402
+ );
403
+ }</pre></div>
404
+
405
+ <div class="tradeoffs">
406
+ <div class="row head">
407
+ <div class="cell">Pro</div>
408
+ <div class="cell">Con</div>
409
+ </div>
410
+ <div class="row">
411
+ <div class="cell pro">leading / trailing / maxWait handled for us</div>
412
+ <div class="cell con">New runtime dependency to audit and update</div>
413
+ </div>
414
+ <div class="row">
415
+ <div class="cell pro">Callback form skips intermediate re-renders</div>
416
+ <div class="cell con">Uncontrolled input diverges from Birchline form patterns</div>
417
+ </div>
418
+ <div class="row">
419
+ <div class="cell pro">Well-tested edge cases (unmount, flush, cancel)</div>
420
+ <div class="cell con">~1.4 kb gzipped for something we could own</div>
421
+ </div>
422
+ </div>
423
+
424
+ <div class="chips">
425
+ <span class="chip">Bundle impact: <strong>+1.4 kb</strong></span>
426
+ <span class="chip">Testability: <strong>high</strong></span>
427
+ <span class="chip">Reuse: <strong>high</strong></span>
428
+ <span class="chip">SSR safe: <strong>yes</strong></span>
429
+ </div>
430
+ </article>
431
+
432
+ </section>
433
+
434
+ <!-- ============================================================= -->
435
+
436
+ <aside class="reco">
437
+ <h2>Recommendation</h2>
438
+ <p>
439
+ Go with <strong>approach 02, the custom <code>useDebounce</code> hook</strong>.
440
+ Birchline already has three places that hand-roll the inline pattern
441
+ (task filter, command palette, member picker), so extracting one
442
+ shared hook removes duplication without taking on a new dependency.
443
+ </p>
444
+ <p>
445
+ Revisit approach 03 only if we later need <code>maxWait</code> or
446
+ <code>flush()</code> semantics — the library earns its bundle cost
447
+ once the requirements outgrow a ten-line hook.
448
+ </p>
449
+ </aside>
450
+
451
+ </div>
452
+ </body>
453
+ </html>