@peaske7/readit 0.3.0-rc.0 → 0.3.0-rc.2

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 (374) hide show
  1. package/README.md +3 -3
  2. package/dist/.vite/manifest.json +1111 -0
  3. package/dist/assets/_basePickBy-BMMA4Tou.js +1 -0
  4. package/dist/assets/_baseUniq-D40qku1I.js +1 -0
  5. package/dist/assets/arc-Ckg65iy8.js +1 -0
  6. package/dist/assets/architecture-YZFGNWBL-Dv3EY0zV.js +1 -0
  7. package/dist/assets/architectureDiagram-Q4EWVU46-DQnkwSaB.js +36 -0
  8. package/dist/assets/array-Bjz-wYpJ.js +1 -0
  9. package/dist/assets/blockDiagram-DXYQGD6D-UB6_S1lm.js +132 -0
  10. package/dist/assets/c4Diagram-AHTNJAMY-sn3k2GND.js +10 -0
  11. package/dist/assets/channel-D9wPw2fQ.js +1 -0
  12. package/dist/assets/chunk-2KRD3SAO-DaFfaCGO.js +1 -0
  13. package/dist/assets/chunk-336JU56O-C8siO5Of.js +2 -0
  14. package/dist/assets/chunk-426QAEUC-BB478m3j.js +1 -0
  15. package/dist/assets/chunk-4BX2VUAB-DRuTD7x5.js +1 -0
  16. package/dist/assets/chunk-4TB4RGXK-_l6jvVAY.js +206 -0
  17. package/dist/assets/chunk-55IACEB6-BExiaAoD.js +1 -0
  18. package/dist/assets/chunk-5FUZZQ4R-HOSFTxuG.js +62 -0
  19. package/dist/assets/chunk-5PVQY5BW-BRVNNRAX.js +2 -0
  20. package/dist/assets/chunk-67CJDMHE-DMt8LNEX.js +1 -0
  21. package/dist/assets/chunk-7N4EOEYR-CzLGefVf.js +1 -0
  22. package/dist/assets/chunk-AA7GKIK3-B6GFAk4U.js +1 -0
  23. package/dist/assets/chunk-BSJP7CBP-BK29yehL.js +1 -0
  24. package/dist/assets/chunk-CIAEETIT-D7hBXImP.js +1 -0
  25. package/dist/assets/chunk-Dlc7tRH4.js +1 -0
  26. package/dist/assets/chunk-EDXVE4YY-CSvKh9DT.js +1 -0
  27. package/dist/assets/chunk-ENJZ2VHE-QApb5cYr.js +10 -0
  28. package/dist/assets/chunk-FMBD7UC4-2FWyCCAV.js +15 -0
  29. package/dist/assets/chunk-FOC6F5B3-DKFHrt4K.js +1 -0
  30. package/dist/assets/chunk-ICPOFSXX-agBjBxsW.js +122 -0
  31. package/dist/assets/chunk-K5T4RW27-D51O7IkG.js +94 -0
  32. package/dist/assets/chunk-KGLVRYIC-DMHSCH4T.js +1 -0
  33. package/dist/assets/chunk-LIHQZDEY-C2aANxt9.js +1 -0
  34. package/dist/assets/chunk-ORNJ4GCN-Db_37NRX.js +1 -0
  35. package/dist/assets/chunk-OYMX7WX6-HGUtT2Q9.js +231 -0
  36. package/dist/assets/chunk-QZHKN3VN-8Lcg9gti.js +1 -0
  37. package/dist/assets/chunk-U2HBQHQK-BFYYQeuC.js +70 -0
  38. package/dist/assets/chunk-X2U36JSP-p8ehTP6s.js +1 -0
  39. package/dist/assets/chunk-XPW4576I-Bqbompq4.js +32 -0
  40. package/dist/assets/chunk-YZCP3GAM-HIMez9pG.js +1 -0
  41. package/dist/assets/chunk-ZZ45TVLE-DRIE_0bu.js +1 -0
  42. package/dist/assets/classDiagram-6PBFFD2Q-BawhEeUl.js +1 -0
  43. package/dist/assets/classDiagram-v2-HSJHXN6E-CLNjgH9n.js +1 -0
  44. package/dist/assets/clone-BBjvuERA.js +1 -0
  45. package/dist/assets/cose-bilkent-S5V4N54A-q90QeGKv.js +1 -0
  46. package/dist/assets/cytoscape.esm-BfXff3fb.js +321 -0
  47. package/dist/assets/dagre-Dxbob2Lr.js +1 -0
  48. package/dist/assets/dagre-KV5264BT-BuvpNxMw.js +4 -0
  49. package/dist/assets/defaultLocale-BwmRmqJp.js +1 -0
  50. package/dist/assets/diagram-5BDNPKRD-DQLsxwwt.js +10 -0
  51. package/dist/assets/diagram-G4DWMVQ6-Jv9Eefw4.js +24 -0
  52. package/dist/assets/diagram-MMDJMWI5-D-0YgNhU.js +43 -0
  53. package/dist/assets/diagram-TYMM5635-BHwO7zQG.js +24 -0
  54. package/dist/assets/dist-BNz65Ibc.js +1 -0
  55. package/dist/assets/erDiagram-SMLLAGMA-BjZGGBJz.js +85 -0
  56. package/dist/assets/flowDiagram-DWJPFMVM-CFbFUm_m.js +162 -0
  57. package/dist/assets/ganttDiagram-T4ZO3ILL-CXk4TcBi.js +292 -0
  58. package/dist/assets/gitGraph-7Q5UKJZL-BGFRt2qs.js +1 -0
  59. package/dist/assets/gitGraphDiagram-UUTBAWPF-C8yZOxjo.js +106 -0
  60. package/dist/assets/graphlib-DGcD9J2L.js +1 -0
  61. package/dist/assets/index-D-m0LiFI.js +14 -0
  62. package/dist/assets/index-DANHO6J0.css +2 -0
  63. package/dist/assets/info-OMHHGYJF-DI6-Z9vh.js +1 -0
  64. package/dist/assets/infoDiagram-42DDH7IO-p-PXDra2.js +2 -0
  65. package/dist/assets/init-TPm5RB77.js +1 -0
  66. package/dist/assets/isArrayLikeObject-69BLnVNM.js +1 -0
  67. package/dist/assets/isEmpty-DUS28g5f.js +1 -0
  68. package/dist/assets/ishikawaDiagram-UXIWVN3A-BrIoEvtb.js +70 -0
  69. package/dist/assets/journeyDiagram-VCZTEJTY-aZpvKa9g.js +139 -0
  70. package/dist/assets/kanban-definition-6JOO6SKY-CoOAY9ji.js +89 -0
  71. package/dist/assets/katex-5SGEXwpi.js +261 -0
  72. package/dist/assets/line-4MF1lR4d.js +1 -0
  73. package/dist/assets/linear-CXMqTN8N.js +1 -0
  74. package/dist/assets/mermaid-config-C8a4L22x.js +1 -0
  75. package/dist/assets/mermaid-parser.core-DREsY2u4.js +4 -0
  76. package/dist/assets/mermaid.core-8ysLpTJi.js +11 -0
  77. package/dist/assets/mindmap-definition-QFDTVHPH-CsqUJCMn.js +96 -0
  78. package/dist/assets/ordinal-D7l-8DAO.js +1 -0
  79. package/dist/assets/packet-4T2RLAQJ-DidW3JFc.js +1 -0
  80. package/dist/assets/path-BVpCanzE.js +1 -0
  81. package/dist/assets/pie-ZZUOXDRM-Bff2e5hg.js +1 -0
  82. package/dist/assets/pieDiagram-DEJITSTG-k0Br4NDS.js +30 -0
  83. package/dist/assets/quadrantDiagram-34T5L4WZ-Be9oCSza.js +7 -0
  84. package/dist/assets/radar-PYXPWWZC-CsdZBH3M.js +1 -0
  85. package/dist/assets/requirementDiagram-MS252O5E-8ECT7dEs.js +84 -0
  86. package/dist/assets/rough.esm-BoTisKeL.js +1 -0
  87. package/dist/assets/sankeyDiagram-XADWPNL6-CoKpeJJ0.js +10 -0
  88. package/dist/assets/sequenceDiagram-FGHM5R23-BTT2fFxG.js +157 -0
  89. package/dist/assets/src-CrmkjRpa.js +1 -0
  90. package/dist/assets/stateDiagram-FHFEXIEX-CIF47NYe.js +1 -0
  91. package/dist/assets/stateDiagram-v2-QKLJ7IA2-Cy1rmPfG.js +1 -0
  92. package/dist/assets/timeline-definition-GMOUNBTQ-Bes4B58n.js +120 -0
  93. package/dist/assets/treeView-SZITEDCU-DPKseaET.js +1 -0
  94. package/dist/assets/treemap-W4RFUUIX-DH-7GZe_.js +1 -0
  95. package/dist/assets/vennDiagram-DHZGUBPP-3wx2huKk.js +34 -0
  96. package/dist/assets/wardley-RL74JXVD-AgyXyBN5.js +1 -0
  97. package/dist/assets/wardleyDiagram-NUSXRM2D-DzViT1Yx.js +20 -0
  98. package/dist/assets/xychartDiagram-5P7HB3ND-BO_dbU0r.js +7 -0
  99. package/{index.html → dist/index.html} +2 -1
  100. package/dist/index.js +2625 -0
  101. package/package.json +11 -1
  102. package/.agents/skills/remotion-best-practices/SKILL.md +0 -61
  103. package/.agents/skills/remotion-best-practices/rules/3d.md +0 -86
  104. package/.agents/skills/remotion-best-practices/rules/animations.md +0 -27
  105. package/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +0 -178
  106. package/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +0 -100
  107. package/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +0 -108
  108. package/.agents/skills/remotion-best-practices/rules/assets.md +0 -78
  109. package/.agents/skills/remotion-best-practices/rules/audio-visualization.md +0 -198
  110. package/.agents/skills/remotion-best-practices/rules/audio.md +0 -169
  111. package/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +0 -134
  112. package/.agents/skills/remotion-best-practices/rules/can-decode.md +0 -75
  113. package/.agents/skills/remotion-best-practices/rules/charts.md +0 -120
  114. package/.agents/skills/remotion-best-practices/rules/compositions.md +0 -154
  115. package/.agents/skills/remotion-best-practices/rules/display-captions.md +0 -184
  116. package/.agents/skills/remotion-best-practices/rules/extract-frames.md +0 -229
  117. package/.agents/skills/remotion-best-practices/rules/ffmpeg.md +0 -38
  118. package/.agents/skills/remotion-best-practices/rules/fonts.md +0 -152
  119. package/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +0 -58
  120. package/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +0 -68
  121. package/.agents/skills/remotion-best-practices/rules/get-video-duration.md +0 -60
  122. package/.agents/skills/remotion-best-practices/rules/gifs.md +0 -141
  123. package/.agents/skills/remotion-best-practices/rules/images.md +0 -134
  124. package/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +0 -69
  125. package/.agents/skills/remotion-best-practices/rules/light-leaks.md +0 -73
  126. package/.agents/skills/remotion-best-practices/rules/lottie.md +0 -70
  127. package/.agents/skills/remotion-best-practices/rules/maps.md +0 -412
  128. package/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +0 -34
  129. package/.agents/skills/remotion-best-practices/rules/measuring-text.md +0 -140
  130. package/.agents/skills/remotion-best-practices/rules/parameters.md +0 -109
  131. package/.agents/skills/remotion-best-practices/rules/sequencing.md +0 -118
  132. package/.agents/skills/remotion-best-practices/rules/sfx.md +0 -26
  133. package/.agents/skills/remotion-best-practices/rules/subtitles.md +0 -36
  134. package/.agents/skills/remotion-best-practices/rules/tailwind.md +0 -11
  135. package/.agents/skills/remotion-best-practices/rules/text-animations.md +0 -20
  136. package/.agents/skills/remotion-best-practices/rules/timing.md +0 -179
  137. package/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +0 -70
  138. package/.agents/skills/remotion-best-practices/rules/transitions.md +0 -197
  139. package/.agents/skills/remotion-best-practices/rules/transparent-videos.md +0 -106
  140. package/.agents/skills/remotion-best-practices/rules/trimming.md +0 -51
  141. package/.agents/skills/remotion-best-practices/rules/videos.md +0 -171
  142. package/.agents/skills/remotion-best-practices/rules/voiceover.md +0 -99
  143. package/.agents/skills/simple/SKILL.md +0 -52
  144. package/.agents/skills/vercel-react-best-practices/AGENTS.md +0 -3254
  145. package/.agents/skills/vercel-react-best-practices/README.md +0 -123
  146. package/.agents/skills/vercel-react-best-practices/SKILL.md +0 -141
  147. package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  148. package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +0 -42
  149. package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -39
  150. package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
  151. package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
  152. package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -51
  153. package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
  154. package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
  155. package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
  156. package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
  157. package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
  158. package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  159. package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
  160. package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
  161. package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
  162. package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
  163. package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
  164. package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -107
  165. package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
  166. package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
  167. package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
  168. package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
  169. package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
  170. package/.agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +0 -60
  171. package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
  172. package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
  173. package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
  174. package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
  175. package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
  176. package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
  177. package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
  178. package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  179. package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
  180. package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
  181. package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  182. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  183. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +0 -30
  184. package/.agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +0 -85
  185. package/.agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +0 -68
  186. package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
  187. package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +0 -75
  188. package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
  189. package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
  190. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +0 -40
  191. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
  192. package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
  193. package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  194. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +0 -38
  195. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
  196. package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +0 -45
  197. package/.agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +0 -82
  198. package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +0 -35
  199. package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
  200. package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +0 -73
  201. package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
  202. package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +0 -96
  203. package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
  204. package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
  205. package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +0 -65
  206. package/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +0 -142
  207. package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
  208. package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
  209. package/.claude/CLAUDE.md +0 -184
  210. package/.claude/commands/review.md +0 -120
  211. package/.claude/commands/sync-docs.md +0 -71
  212. package/.claude/roadmap.md +0 -121
  213. package/.claude/rules/style-guide.md +0 -830
  214. package/.claude/settings.json +0 -18
  215. package/.claude/user-stories.md +0 -333
  216. package/AGENTS.md +0 -68
  217. package/Makefile +0 -32
  218. package/biome.json +0 -79
  219. package/bun.lock +0 -854
  220. package/bunfig.toml +0 -2
  221. package/docs/design.md +0 -563
  222. package/docs/perf-baseline.md +0 -130
  223. package/docs/plans/2026-03-13-client-mode-design.md +0 -86
  224. package/docs/plans/2026-03-13-client-mode-plan.md +0 -605
  225. package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +0 -129
  226. package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +0 -1471
  227. package/docs/plans/2026-03-13-multi-document-design.md +0 -183
  228. package/docs/plans/2026-03-13-performance-benchmarks-design.md +0 -121
  229. package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +0 -1176
  230. package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +0 -284
  231. package/e2e/comments.spec.ts +0 -81
  232. package/e2e/document-load.spec.ts +0 -32
  233. package/e2e/export.spec.ts +0 -58
  234. package/e2e/fixtures/sample.md +0 -7
  235. package/e2e/perf/add-comment.spec.ts +0 -116
  236. package/e2e/perf/fixtures/generate.ts +0 -327
  237. package/e2e/perf/initial-load.spec.ts +0 -49
  238. package/e2e/perf/perf.setup.ts +0 -23
  239. package/e2e/perf/perf.teardown.ts +0 -9
  240. package/e2e/perf/screenshot-final.png +0 -0
  241. package/e2e/perf/scroll.spec.ts +0 -39
  242. package/e2e/perf/tab-switch.spec.ts +0 -69
  243. package/e2e/perf/text-selection.spec.ts +0 -119
  244. package/e2e/perf/utils/metrics.ts +0 -350
  245. package/e2e/perf/utils/perf-cli.ts +0 -86
  246. package/e2e/persistence-file.spec.ts +0 -357
  247. package/e2e/utils/cli.ts +0 -84
  248. package/e2e/utils/selection.ts +0 -79
  249. package/go/cmd/readit/main.go +0 -416
  250. package/go/go.mod +0 -20
  251. package/go/go.sum +0 -41
  252. package/go/internal/server/anchor.go +0 -302
  253. package/go/internal/server/anchor_test.go +0 -111
  254. package/go/internal/server/comments.go +0 -390
  255. package/go/internal/server/documents.go +0 -113
  256. package/go/internal/server/embed.go +0 -17
  257. package/go/internal/server/headings.go +0 -33
  258. package/go/internal/server/headings_test.go +0 -75
  259. package/go/internal/server/htmltext.go +0 -123
  260. package/go/internal/server/markdown.go +0 -157
  261. package/go/internal/server/markdown_bench_test.go +0 -42
  262. package/go/internal/server/markdown_test.go +0 -79
  263. package/go/internal/server/server.go +0 -453
  264. package/go/internal/server/server_bench_test.go +0 -122
  265. package/go/internal/server/settings.go +0 -110
  266. package/go/internal/server/sse.go +0 -140
  267. package/go/internal/server/storage.go +0 -275
  268. package/go/internal/server/storage_test.go +0 -152
  269. package/go/internal/server/template.go +0 -66
  270. package/go/internal/server/types.go +0 -101
  271. package/go/internal/server/watcher.go +0 -74
  272. package/lefthook.yml +0 -8
  273. package/nvim-readit/lua/readit/health.lua +0 -64
  274. package/nvim-readit/lua/readit/init.lua +0 -463
  275. package/nvim-readit/plugin/readit.lua +0 -19
  276. package/playwright.config.ts +0 -34
  277. package/skills-lock.json +0 -20
  278. package/src/App.svelte +0 -890
  279. package/src/cli.ts +0 -881
  280. package/src/components/ActionsMenu.svelte +0 -95
  281. package/src/components/CommentBadge.svelte +0 -67
  282. package/src/components/CommentErrorBanner.svelte +0 -33
  283. package/src/components/CommentInput.svelte +0 -75
  284. package/src/components/CommentListItem.svelte +0 -95
  285. package/src/components/CommentManager.svelte +0 -129
  286. package/src/components/CommentNav.svelte +0 -109
  287. package/src/components/DocumentViewer.svelte +0 -233
  288. package/src/components/FloatingComment.svelte +0 -107
  289. package/src/components/Header.svelte +0 -76
  290. package/src/components/InlineEditor.svelte +0 -72
  291. package/src/components/MarginNote.svelte +0 -167
  292. package/src/components/MarginNotesContainer.svelte +0 -33
  293. package/src/components/MermaidEnhancer.svelte +0 -218
  294. package/src/components/MermaidModal.svelte +0 -67
  295. package/src/components/RawModal.svelte +0 -126
  296. package/src/components/ReanchorConfirm.svelte +0 -30
  297. package/src/components/SettingsModal.svelte +0 -220
  298. package/src/components/ShortcutCapture.svelte +0 -82
  299. package/src/components/ShortcutList.svelte +0 -145
  300. package/src/components/TabBar.svelte +0 -52
  301. package/src/components/TableOfContents.svelte +0 -125
  302. package/src/components/ui/ActionLink.svelte +0 -40
  303. package/src/components/ui/Button.svelte +0 -53
  304. package/src/components/ui/Dialog.svelte +0 -97
  305. package/src/components/ui/DropdownMenu.svelte +0 -85
  306. package/src/components/ui/DropdownMenuItem.svelte +0 -38
  307. package/src/components/ui/DropdownMenuSeparator.svelte +0 -11
  308. package/src/components/ui/Text.svelte +0 -42
  309. package/src/env.d.ts +0 -6
  310. package/src/index.css +0 -859
  311. package/src/lib/__fixtures__/bench-data.ts +0 -114
  312. package/src/lib/anchor.bench.ts +0 -91
  313. package/src/lib/anchor.test.ts +0 -527
  314. package/src/lib/anchor.ts +0 -381
  315. package/src/lib/comment-storage.bench.ts +0 -49
  316. package/src/lib/comment-storage.test.ts +0 -694
  317. package/src/lib/comment-storage.ts +0 -226
  318. package/src/lib/export.bench.ts +0 -21
  319. package/src/lib/export.ts +0 -36
  320. package/src/lib/fetch-or-throw.test.ts +0 -59
  321. package/src/lib/fetch-or-throw.ts +0 -12
  322. package/src/lib/headings.test.ts +0 -103
  323. package/src/lib/headings.ts +0 -44
  324. package/src/lib/highlight/core.test.ts +0 -93
  325. package/src/lib/highlight/dom.ts +0 -187
  326. package/src/lib/highlight/highlight-registry.ts +0 -221
  327. package/src/lib/highlight/highlight.bench.ts +0 -92
  328. package/src/lib/highlight/highlighter.ts +0 -247
  329. package/src/lib/highlight/resolver.ts +0 -38
  330. package/src/lib/highlight/types.ts +0 -17
  331. package/src/lib/html-text.test.ts +0 -162
  332. package/src/lib/html-text.ts +0 -161
  333. package/src/lib/i18n/en.ts +0 -124
  334. package/src/lib/i18n/index.ts +0 -3
  335. package/src/lib/i18n/ja.ts +0 -126
  336. package/src/lib/i18n/translations.ts +0 -27
  337. package/src/lib/i18n/types.ts +0 -130
  338. package/src/lib/key-lock.test.ts +0 -104
  339. package/src/lib/key-lock.ts +0 -23
  340. package/src/lib/margin-layout.bench.ts +0 -61
  341. package/src/lib/margin-layout.ts +0 -71
  342. package/src/lib/markdown-renderer.test.ts +0 -154
  343. package/src/lib/markdown-renderer.ts +0 -178
  344. package/src/lib/mermaid-config.ts +0 -38
  345. package/src/lib/mermaid-renderer.ts +0 -162
  346. package/src/lib/mermaid-worker.ts +0 -60
  347. package/src/lib/positions.ts +0 -157
  348. package/src/lib/shortcut-registry.ts +0 -244
  349. package/src/lib/utils.ts +0 -15
  350. package/src/main.ts +0 -16
  351. package/src/schema.ts +0 -92
  352. package/src/server.ts +0 -1216
  353. package/src/stores/app.svelte.ts +0 -231
  354. package/src/stores/locale.svelte.ts +0 -46
  355. package/src/stores/settings.svelte.ts +0 -90
  356. package/src/stores/shortcuts.svelte.ts +0 -104
  357. package/src/stores/ui.svelte.ts +0 -12
  358. package/src/template.ts +0 -104
  359. package/src/test-setup.ts +0 -48
  360. package/svelte.config.js +0 -5
  361. package/test.md +0 -74
  362. package/tsconfig.cli.json +0 -12
  363. package/tsconfig.json +0 -20
  364. package/vite.config.ts +0 -47
  365. package/vitest.config.ts +0 -15
  366. package/vscode-readit/.mcp.json +0 -7
  367. package/vscode-readit/.vscodeignore +0 -7
  368. package/vscode-readit/bun.lock +0 -78
  369. package/vscode-readit/icon.svg +0 -10
  370. package/vscode-readit/package.json +0 -110
  371. package/vscode-readit/src/extension.ts +0 -117
  372. package/vscode-readit/src/server-manager.ts +0 -272
  373. package/vscode-readit/src/webview-provider.ts +0 -204
  374. package/vscode-readit/tsconfig.json +0 -20
@@ -1,830 +0,0 @@
1
- # Style Guide
2
-
3
- ## 1 Introduction
4
-
5
- Good software is fast, reliable, and beautiful.
6
- Fast software requires focus and clarity.
7
- Reliable software requires simplicity and correctness.
8
- Beautiful software requires attention to detail and aesthetics.
9
-
10
- ### 1.1 Core Philosophy
11
-
12
- - **Obvious over clever** - Code should be immediately understandable and easy to reason about. The wtf metric should be less than 1.
13
- - **Code is a liability** - Less code means fewer bugs, easier maintenance, and faster comprehension. Also, well written code is usually shorter, since it's much more focused and concise.
14
- - **Type safety without overhead** - Leverage TypeScript without runtime cost
15
- - **Work with the language, not against it** - Prefer native JavaScript patterns over complex type gymnastics. My aversion to complex libraries like rxjs, effect-ts, functional-ts stems from the fact that force the writer a "non-native" way of thinking about the language.
16
- - **Immutability by default** - Prevent bugs through data flow clarity
17
- - **Composition over inheritance** - Build complex behavior from simple, reusable parts
18
- - **Fail fast, recover explicitly** - Make error states visible and recoverable
19
- - **Duplication over wrong abstraction** - Write it twice, abstract on the third time
20
- - **Practicality over rigid rules** - Adapt patterns to the problem at hand
21
- - **Tight, clean implementations** - Functions should do their job without ceremony or overhead
22
-
23
- ## 2 Type Design
24
-
25
- ### 2.1 Parse, Don't Validate
26
-
27
- **Why:** Transform data into types that make invalid states impossible, but stay pragmatic with native JavaScript types.
28
-
29
- ```ts
30
- // Good - parse into a stronger type, but keep it simple
31
- function parseUserId(input: unknown): string {
32
- if (typeof input !== 'string' || !input.startsWith('user_')) {
33
- throw new Error('Invalid user ID format');
34
- }
35
- return input; // Now we know it's a valid user ID
36
- }
37
-
38
- // Good - use native JavaScript types
39
- function parseDate(input: string): Date {
40
- const date = new Date(input);
41
- if (isNaN(date.getTime())) {
42
- throw new Error('Invalid date');
43
- }
44
- return date;
45
- }
46
-
47
- // Bad - bespoke utility types that fight the language
48
- type ParsedString<T extends string> = T extends `${infer P}` ? P : never;
49
- type ValidatedNumber<Min extends number, Max extends number> = number & {
50
- __min: Min;
51
- __max: Max;
52
- };
53
-
54
- // Bad - validate repeatedly instead of parsing once
55
- function processUser(userId: string) {
56
- if (!isValidUserId(userId)) throw new Error();
57
- // ... later in code
58
- if (!isValidUserId(userId)) throw new Error(); // Validating again!
59
- }
60
- ```
61
-
62
- ### 2.2 Discriminated Unions Over Classes
63
-
64
- **Why:** Exhaustive checking, no inheritance complexity, better tree-shaking.
65
-
66
- ```ts
67
- // Good
68
- type State =
69
- | { status: 'idle' }
70
- | { status: 'loading' }
71
- | { status: 'success'; data: User[] }
72
- | { status: 'error'; error: Error };
73
-
74
- // Bad - classes add unnecessary complexity
75
- class IdleState {}
76
- class LoadingState {}
77
- class SuccessState { constructor(public data: User[]) {} }
78
- ```
79
-
80
- Exception: use classes only for stateful services with multiple collaborators (dependency injection). Do not model state machines or data with classes. See 8.1.
81
-
82
- ### 2.3 Null vs Undefined Policy
83
-
84
- - Prefer `undefined` for optionals; optional means possibly `undefined`, not `null`.
85
- - Normalize external `null` at the boundary (e.g., request parsing, DB hydration).
86
- - `tsconfig`: ensure `exactOptionalPropertyTypes: true`.
87
-
88
- ## 3 Function Design
89
-
90
- ### 3.1 Simple Over Complex
91
-
92
- **Why:** Prefer simple, imperative solutions over complex functional abstractions. Every line of code is a potential bug and requires maintenance.
93
-
94
- ```ts
95
- // Good - simple, imperative, easy to follow
96
- async function processUsers(users: User[]) {
97
- const results = [] as ProcessedUser[];
98
- for (const user of users) {
99
- if (user.isActive) {
100
- const processed = await processUser(user);
101
- results.push(processed);
102
- }
103
- }
104
- return results;
105
- }
106
-
107
- // Bad - complex functional approach with custom types
108
- const processUsersFancy = pipe(
109
- filter(isActive),
110
- traverseArray(Task.of),
111
- map(processUser),
112
- sequence(Task),
113
- fold(
114
- onError(handleError),
115
- onSuccess(identity)
116
- )
117
- );
118
-
119
- // Bad - TypeScript gymnastics that fight the language
120
- type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
121
-
122
- type RecursiveRequired<T> = T extends object ? { [P in keyof T]-?: RecursiveRequired<T[P]> } : T;
123
-
124
- // Good - use built-in types or simple solutions
125
- type UserUpdate = Partial<User>;
126
- ```
127
-
128
- ### 3.2 Balanced Function Decomposition
129
-
130
- **Why:** Balance between monolithic functions and over-abstraction. Extract functions only when they encapsulate meaningful complexity or represent distinct business operations.
131
-
132
- Extract when:
133
-
134
- - Function encapsulates 30+ lines of complex logic
135
- - Represents a distinct business operation
136
- - Has multiple responsibilities that can be separated
137
- - Contains complex error handling or transaction management
138
- - Introduces a second abstraction level (loops inside loops, nested conditions)
139
-
140
- Inline when:
141
-
142
- - Simple string manipulation (`name.replace(/_id_seq$/, "")`)
143
- - Basic array operations (`array.filter(condition)`)
144
- - Trivial database query wrappers
145
- - One-line utility functions
146
- - Operations used only once
147
-
148
- ```ts
149
- // Bad - excessive abstractions for trivial operations
150
- function parseSequenceName(name: string): string {
151
- return name.replace(/_id_seq$/, "");
152
- }
153
-
154
- function findCommonColumns(source: string[], target: string[]): string[] {
155
- return source.filter(col => target.includes(col));
156
- }
157
-
158
- // Good - inline trivial operations
159
- async function resetSequences(targetClient: PoolClient): Promise<void> {
160
- const sequencesResult = await targetClient.query(/* ... */);
161
-
162
- for (const seq of sequencesResult.rows) {
163
- const tableName = seq.sequence_name.replace(/_id_seq$/, ""); // Inline simple operations
164
- await targetClient.query(/* ... */);
165
- }
166
- }
167
-
168
- // Good - extract when there's meaningful complexity
169
- async function syncTableData(
170
- sourceClient: PoolClient,
171
- targetClient: PoolClient,
172
- table: string,
173
- signal: AbortSignal,
174
- ): Promise<{ skipped: boolean }> {
175
- // 40+ lines of complex column mapping, data streaming, error handling
176
- // This encapsulates meaningful business logic
177
- return { skipped: false };
178
- }
179
-
180
- // Bad - monolithic function doing too much
181
- function seedDatabaseAndValidateAndLogAndEmailAndCreateBackup() {
182
- // 230+ lines of nested logic...
183
- }
184
-
185
- // Good - clean orchestration using meaningful business operations
186
- async function seedFromStaging(signal: AbortSignal): Promise<void> {
187
- let sourceClient: PoolClient | null = null;
188
- let targetClient: PoolClient | null = null;
189
-
190
- try {
191
- const sourcePool = new Pool({ connectionString: config.staging.directUrl });
192
- sourceClient = await sourcePool.connect();
193
-
194
- // Clear orchestration of business operations
195
- for (const table of tables) {
196
- await syncTableData(sourceClient, targetClient!, table, signal);
197
- }
198
- await resetSequences(targetClient!);
199
- await createSupabaseUsers(targetClient!);
200
- } finally {
201
- // ...
202
- }
203
- }
204
- ```
205
-
206
- ### 3.3 Early Returns and Guard Clauses
207
-
208
- **Why:** Reduces nesting, puts happy path at root indentation, fails fast.
209
-
210
- ```ts
211
- // Good
212
- function processUser(user: User | undefined): ProcessedUser {
213
- if (!user) {
214
- throw new Error('User is required');
215
- }
216
-
217
- if (!user.isActive) {
218
- throw new Error('User is inactive');
219
- }
220
-
221
- // Happy path at root indentation
222
- return transform(user);
223
- }
224
-
225
- // Bad - nested pyramid of doom
226
- function processUserNested(user: User | undefined): ProcessedUser {
227
- if (user) {
228
- if (user.isActive) {
229
- return transform(user);
230
- } else {
231
- throw new Error('User is inactive');
232
- }
233
- } else {
234
- throw new Error('User is required');
235
- }
236
- }
237
- ```
238
-
239
- #### 3.3.1 Prefer Early Returns Over Accumulating State with let
240
-
241
- Avoid patterns where you declare `let` variables and then mutate them across large `if/else` blocks. Instead, compute and return early for each branch; this keeps logic linear and types narrower.
242
-
243
- ```ts
244
- // Bad - accumulating state with let + if/else chains
245
- let template: Template | null = null;
246
- let sections: Section[] = [];
247
- if (forcedTemplateId) {
248
- template = await fetchTemplate(forcedTemplateId);
249
- sections = await fetchSections(template.id);
250
- } else {
251
- const inferred = await inferTemplate(transcript);
252
- template = await fetchTemplate(inferred.id);
253
- sections = await fetchSections(template.id);
254
- }
255
- return { template: template!, sections };
256
-
257
- // Good - early returns per branch
258
- if (forcedTemplateId) {
259
- const template = await fetchTemplate(forcedTemplateId);
260
- const sections = await fetchSections(template.id);
261
- return { template, sections };
262
- }
263
- const inferred = await inferTemplate(transcript);
264
- const template = await fetchTemplate(inferred.id);
265
- const sections = await fetchSections(template.id);
266
- return { template, sections };
267
- ```
268
-
269
- ### 3.4 Naming Conventions
270
-
271
- **Why:** Clear, concise names make code self-documenting. Avoid redundancy and be specific.
272
-
273
- ```ts
274
- // Good - concise, clear names
275
- interface User {
276
- id: string;
277
- email: string;
278
- isActive: boolean;
279
- }
280
-
281
- function getUser(id: string): User { /* sync, O(1) lookup only */ }
282
- async function fetchUser(id: string): Promise<User> { /* I/O */ }
283
- async function loadDashboard(id: string): Promise<Dashboard> { /* I/O + compose */ }
284
- async function saveUser(id: string, data: Partial<User>): Promise<void> { /* writes */ }
285
-
286
- // Boolean naming - use is/has/can/should prefixes
287
- const isActive = user.status === 'active';
288
- const hasPermission = user.roles.includes('admin');
289
- const canEdit = user.permissions.edit;
290
- const shouldRetry = attempts < maxRetries;
291
-
292
- // Time units - suffix with Ms for numbers representing milliseconds
293
- const retryDelayMs = 250;
294
- const timeoutMs = 5_000;
295
-
296
- // Bad - redundant or unclear names
297
- interface UserInterface { } // Don't suffix interfaces
298
- class UserClass { } // Don't suffix classes
299
- type UserType = { } // Don't suffix types
300
-
301
- function getUserById(userId: string) { } // Prefer this shape only when multiple selectors exist (e.g. getUser, getUserByEmail)
302
- function processUserDataAndSave() { } // Too vague, doing multiple things
303
- function doStuff() { } // Meaningless
304
-
305
- // Bad - Hungarian notation or type prefixes
306
- const strName = 'John'; // Type is obvious from TypeScript
307
- const arrUsers = []; // Don't encode type in name
308
- const objConfig = {}; // Let TypeScript handle types
309
-
310
- // Good - array naming is plural, clear what it contains
311
- const users: User[] = [];
312
- const activeUsers = users.filter(u => u.isActive);
313
-
314
- // Bad - unclear array naming
315
- const userArray: User[] = [];
316
- const data = users.filter(u => u.isActive); // What data?
317
- ```
318
-
319
- ### 3.5 Object Destructuring for Opaque Parameters
320
-
321
- **Why:** When a parameter's type (e.g., `string`, `number`) doesn't convey its purpose, use object destructuring to make the function self-documenting.
322
-
323
- ```ts
324
- // Good - parameter name is part of the signature
325
- function subscribe({ eventId }: { eventId: string }): void { }
326
- function fetchUser({ userId }: { userId: string }): Promise<User> { }
327
- function cancelBot({ eventId }: { eventId: string }): Promise<void> { }
328
-
329
- // Bad - "string" tells you nothing
330
- function subscribe(eventId: string): void { }
331
- function fetchUser(userId: string): Promise<User> { }
332
- function cancelBot(eventId: string): Promise<void> { }
333
- ```
334
-
335
- **When to apply:**
336
-
337
- - Single string/number parameters where the type doesn't describe the value
338
- - Multiple parameters of the same type (avoids argument order confusion)
339
- - Public API boundaries (interfaces, exported functions)
340
-
341
- **When to skip:**
342
-
343
- - Private helper methods with obvious context
344
- - Standard patterns like `formatDate(date: Date)` where the type is descriptive
345
- - Primitive math utilities like `clamp(value: number, min: number, max: number)`
346
-
347
- ## 4 Error Handling
348
-
349
- ### 4.1 Single Try-Catch for Related Operations
350
-
351
- **Why:** Group related operations in a single try-catch block for cleaner error handling. Avoid multiple try-catch blocks at the same level - extract operations to separate functions instead.
352
-
353
- ```ts
354
- // Good - single try-catch for related operations
355
- async function disconnectCalendar(calendarId: string) {
356
- try {
357
- const calendar = await getCalendar(calendarId);
358
- if (!calendar) {
359
- throw new NotFoundError('Calendar not found');
360
- }
361
-
362
- const events = await getEvents(calendar.id);
363
-
364
- // All related operations in one try block
365
- for (const event of events) {
366
- if (event.id) {
367
- await cancelBot(event.id);
368
- await deleteEvent(event.id);
369
- }
370
- }
371
-
372
- await deleteCalendar(calendar.id);
373
- await logAuditEvent(calendar);
374
-
375
- return { success: true, message: 'Calendar disconnected' };
376
- } catch (error) {
377
- logger.error(error, '[disconnect-calendar] error');
378
-
379
- if (error instanceof NotFoundError) {
380
- return { error: error.message, status: 404 };
381
- }
382
-
383
- if (error instanceof BadRequestError) {
384
- return { error: error.message, status: 400 };
385
- }
386
-
387
- return { error: 'Internal server error', status: 500 };
388
- }
389
- }
390
-
391
- // Good - extract unrelated operations to separate functions
392
- async function setupUser(userData: UserData) {
393
- const user = await createUser(userData);
394
-
395
- // Handle optional operations separately
396
- await sendWelcomeEmailSafely(user.email);
397
-
398
- return user;
399
- }
400
-
401
- async function sendWelcomeEmailSafely(email: string) {
402
- try {
403
- await sendWelcomeEmail(email);
404
- } catch (error) {
405
- logger.warn(`Failed to send welcome email to ${email}: ${(error as Error).message}`);
406
- }
407
- }
408
-
409
- // Bad - multiple try-catch blocks for related operations
410
- async function disconnectCalendarManyTrys(calendarId: string) {
411
- let calendar: Calendar;
412
- try {
413
- calendar = await getCalendar(calendarId);
414
- } catch (error) {
415
- return { error: 'Failed to get calendar' };
416
- }
417
-
418
- let events: Event[];
419
- try {
420
- events = await getEvents(calendar.id);
421
- } catch (error) {
422
- return { error: 'Failed to get events' };
423
- }
424
- }
425
- ```
426
-
427
- ## 5 Data Manipulation
428
-
429
- ### 5.1 Immutable Updates vs Imperative Operations
430
-
431
- **Why:** Use immutable updates for data transformations, but imperative patterns are often cleaner for business operations with side effects.
432
-
433
- ```ts
434
- // Good - immutable updates for data transformations
435
- function updateUser(user: User, updates: Partial<User>): User {
436
- return { ...user, ...updates };
437
- }
438
-
439
- // Good - nested immutable updates for pure data operations
440
- function addItemToCart(cart: Cart, item: Item): Cart {
441
- return {
442
- ...cart,
443
- items: [...cart.items, item],
444
- total: cart.total + item.price,
445
- };
446
- }
447
-
448
- // Good - imperative for complex validation with early returns
449
- function validateUserData(data: unknown): User {
450
- if (!data || typeof data !== 'object') {
451
- throw new Error('Invalid data format');
452
- }
453
-
454
- const obj = data as Record<string, unknown>;
455
-
456
- if (!obj.email || typeof obj.email !== 'string') {
457
- throw new Error('Email is required');
458
- }
459
-
460
- if (!obj.name || typeof obj.name !== 'string') {
461
- throw new Error('Name is required');
462
- }
463
-
464
- return { email: obj.email, name: obj.name } as User;
465
- }
466
- ```
467
-
468
- ### 5.2 Choose the Right Iteration Pattern
469
-
470
- **Why:** Different patterns work better in different contexts. Use array methods for pure transformations, loops for side effects and async operations.
471
-
472
- ```ts
473
- // Good - array methods for pure data transformations
474
- const activeAdminEmails = users
475
- .filter(u => u.isActive)
476
- .filter(u => u.role === 'admin')
477
- .map(u => u.email);
478
-
479
- // Good - for loops for async operations with side effects
480
- for (const event of eventsToDelete) {
481
- if (event.id) {
482
- await botsDomain.cancelBotForEvent(event.id);
483
- await calendarStorage.deleteEvent({ eventId: event.id });
484
- }
485
- }
486
-
487
- // Good - for loops when you need sequential processing
488
- for (const user of users) {
489
- if (await shouldProcessUser(user)) {
490
- await processUser(user);
491
- await logUserProcessed(user);
492
- }
493
- }
494
-
495
- // Good - batched processing for high volume
496
- const batchSize = 5;
497
- for (let i = 0; i < users.length; i += batchSize) {
498
- const batch = users.slice(i, i + batchSize);
499
- await Promise.all(batch.map(processUser));
500
- }
501
-
502
- // Bad - array methods that force Promise.all when you need sequential
503
- await Promise.all(
504
- users.map(async user => {
505
- if (await shouldProcessUser(user)) {
506
- await processUser(user); // This runs in parallel, might overwhelm DB
507
- }
508
- })
509
- );
510
-
511
- // Bad - forEach with async callbacks
512
- users.forEach(async user => {
513
- await processUser(user); // fire-and-forget, order not guaranteed
514
- });
515
- ```
516
-
517
- ## 6 Type Utilities
518
-
519
- ### 6.1 Type Predicates for Narrowing
520
-
521
- **Why:** Provides type-safe runtime checks that TypeScript understands.
522
-
523
- ```ts
524
- // Good - type predicate
525
- function isUser(value: unknown): value is User {
526
- return typeof value === 'object' &&
527
- value !== null &&
528
- 'id' in value &&
529
- 'email' in value;
530
- }
531
-
532
- function isError(value: unknown): value is Error {
533
- return value instanceof Error;
534
- }
535
-
536
- // Usage - TypeScript narrows the type
537
- if (isUser(data)) {
538
- console.log(data.email); // TypeScript knows data is User
539
- }
540
- ```
541
-
542
- ### 6.2 Const Assertions
543
-
544
- **Why:** Narrowest possible types without explicit annotation.
545
-
546
- ```ts
547
- // Good
548
- const ROLES = ['admin', 'user', 'guest'] as const;
549
- type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
550
-
551
- // Bad - loses type information
552
- const ROLES2 = ['admin', 'user', 'guest'];
553
- type AnyRole = string; // Too broad
554
- ```
555
-
556
- #### Naming for const-asserted maps and derived unions
557
-
558
- - Keep identifiers distinct for the runtime value and its derived union type (improves readability and import ergonomics).
559
- - Using the same identifier for both a value and a type is valid but discouraged.
560
-
561
- ```ts
562
- // Good - distinct names for value (runtime) and type
563
- export const PublicWorkflowJobTypes = {
564
- TITLE_GENERATION: 'TITLE_GENERATION',
565
- // ... other entries ...
566
- } as const;
567
-
568
- export type PublicWorkflowJobType =
569
- (typeof PublicWorkflowJobTypes)[keyof typeof PublicWorkflowJobTypes];
570
- ```
571
-
572
- ### 6.3 No Enums - Use Const Object Pattern
573
-
574
- **Why:**
575
-
576
- 1. **Single Source of Truth:** The `const` object serves as the single source for both runtime values and type definitions (via `typeof obj[keyof typeof obj]`). This avoids "magic strings" and ensures that type checking is grounded in actual runtime values, unlike raw string unions where type assertions are often the only check.
577
- 2. **Predictability:** Enums effectively introduce a "custom syntax" on top of JavaScript with unintuitive behaviors (like reverse mappings for numeric enums) that many developers find confusing. `const` objects are just standard JavaScript.
578
- 3. **Performance:** `const` objects compile to simple objects (or plain literals if inlined), ensuring zero runtime overhead and optimal tree-shaking, though the semantic benefits above are the primary driver.
579
-
580
- **Pattern:**
581
-
582
- ```ts
583
- // Good - const object + derived type
584
- export const PublicVideoStatuses = {
585
- UPLOADING: "UPLOADING",
586
- CREATED: "CREATED",
587
- READY: "READY",
588
- ERROR: "ERROR",
589
- ARCHIVED: "ARCHIVED",
590
- } as const;
591
- export type PublicVideoStatus =
592
- (typeof PublicVideoStatuses)[keyof typeof PublicVideoStatuses];
593
-
594
- // Bad - TypeScript enum
595
- enum VideoStatus {
596
- UPLOADING = "UPLOADING",
597
- CREATED = "CREATED",
598
- // ...
599
- }
600
-
601
- // Bad - plain union (no runtime values)
602
- type VideoStatus = "UPLOADING" | "CREATED" | "READY" | "ERROR" | "ARCHIVED";
603
- ```
604
-
605
- **Naming convention:**
606
-
607
- - **Const object**: Plural form (`PublicVideoStatuses`, `PublicJobExecutionStatuses`)
608
- - **Derived type**: Singular form (`PublicVideoStatus`, `PublicJobExecutionStatus`)
609
- - Keep both names distinct for clear imports
610
-
611
- **Usage:**
612
-
613
- ```ts
614
- // Runtime comparison (use the const object)
615
- if (video.status === PublicVideoStatuses.READY) { ... }
616
-
617
- // Type annotation (use the derived type)
618
- function processVideo(status: PublicVideoStatus): void { ... }
619
-
620
- // Exhaustive switch
621
- function getStatusLabel(status: PublicVideoStatus): string {
622
- switch (status) {
623
- case PublicVideoStatuses.UPLOADING: return "アップロード中";
624
- case PublicVideoStatuses.READY: return "準備完了";
625
- // TypeScript ensures all cases are covered
626
- }
627
- }
628
- ```
629
-
630
- **Reference:** See `apps/web/src/lib/domain/` for canonical examples (e.g., `video/video.ts`, `job/job.ts`, `workflow/workflow.ts`).
631
-
632
- ## 7 Async Patterns
633
-
634
- ### 7.1 Async/Await Over Promise Chains
635
-
636
- **Why:** Linear flow, better error handling, easier debugging.
637
-
638
- ```ts
639
- // Good
640
- async function fetchUserData(id: string): Promise<UserData> {
641
- const user = await fetchUser(id);
642
- const profile = await fetchProfile(user.profileId);
643
- return { user, profile };
644
- }
645
-
646
- // Bad - callback hell
647
- function fetchUserDataChained(id: string): Promise<UserData> {
648
- return fetchUser(id)
649
- .then(user => fetchProfile(user.profileId)
650
- .then(profile => ({ user, profile })));
651
- }
652
- ```
653
-
654
- ### 7.2 Sequential vs Parallel Processing
655
-
656
- **Why:** Sequential processing is safer for side effects, parallel processing is better for independent operations. Choose based on the operation's requirements.
657
-
658
- ```ts
659
- // Good - sequential for operations with side effects
660
- async function disconnectCalendar(calendarId: string) {
661
- const eventsToDelete = await getEvents(calendarId);
662
-
663
- // Process events sequentially to avoid overwhelming external APIs
664
- for (const event of eventsToDelete) {
665
- if (event.id) {
666
- await cancelBotForEvent(event.id); // External API call
667
- await deleteEventFromDB(event.id); // Database operation
668
- }
669
- }
670
-
671
- await deleteCalendar(calendarId);
672
- }
673
-
674
- // Bad - parallel processing that can overwhelm external services
675
- async function disconnectCalendarParallel(calendarId: string) {
676
- const eventsToDelete = await getEvents(calendarId);
677
- await Promise.allSettled(
678
- eventsToDelete.map(async event => {
679
- if (event.id) {
680
- await cancelBotForEvent(event.id);
681
- await deleteEventFromDB(event.id);
682
- }
683
- })
684
- );
685
- }
686
- ```
687
-
688
- ## 8 Module Design
689
-
690
- ### 8.1 Dependency Injection Over Singletons
691
-
692
- **Why:** Testable, configurable, explicit dependencies. Classes work well for stateful services with multiple dependencies.
693
-
694
- ```ts
695
- // Good - class-based dependency injection for complex services
696
- export class RecallCalendarsApiImpl implements RecallCalendarsApi {
697
- constructor(
698
- private readonly router: Router = express.Router(),
699
- private readonly calendarStorage: CalendarStorage,
700
- private readonly recallApiClient: RecallApiClient,
701
- private readonly auditLogger: AuditLogger,
702
- private readonly botsDomain: BotsDomain,
703
- private readonly logger: Logger,
704
- ) {}
705
-
706
- async disconnectCalendar(calendarId: string) {
707
- this.logger.debug('[disconnect-calendar] request received');
708
-
709
- try {
710
- const calendar = await this.calendarStorage.getCalendar(calendarId);
711
- const events = await this.calendarStorage.getEvents(calendar.id);
712
-
713
- for (const event of events) {
714
- await this.botsDomain.cancelBot(event.id);
715
- await this.calendarStorage.deleteEvent(event.id);
716
- }
717
-
718
- await this.recallApiClient.deleteCalendar(calendar.recall_id);
719
- await this.auditLogger.log({ action: 'calendar.disconnected' });
720
-
721
- } catch (error) {
722
- this.logger.error(error, '[disconnect-calendar] error');
723
- throw error;
724
- }
725
- }
726
- }
727
-
728
- // Good - functional approach for simpler operations
729
- interface UserRepository {
730
- create(user: User): Promise<User>;
731
- findById(id: string): Promise<User | null>;
732
- }
733
-
734
- interface Logger {
735
- info(message: string): void;
736
- error(error: Error, message: string): void;
737
- }
738
-
739
- async function createUser(
740
- userData: UserData,
741
- userRepo: UserRepository,
742
- logger: Logger,
743
- ): Promise<User> {
744
- logger.info(`Creating user: ${userData.email}`);
745
-
746
- try {
747
- return await userRepo.create(userData);
748
- } catch (error) {
749
- logger.error(error as Error, 'Failed to create user');
750
- throw error;
751
- }
752
- }
753
- ```
754
-
755
- ### 8.3 Exports & File Naming
756
-
757
- - Prefer named exports in backend code; reach for default exports only when the module truly presents a single concept (config, logger, etc.).
758
- - Top-level names: PascalCase for types/classes, camelCase for values.
759
- - One module = one responsibility; avoid `index.ts` barrels unless they truly clarify usage.
760
-
761
- ## 9 Refactoring Approach
762
-
763
- ### 9.1 From Monolith to Balanced Decomposition
764
-
765
- **Why:** Large monolithic functions are hard to understand, but excessive decomposition creates indirection. Strike a balance by extracting functions that represent meaningful business operations.
766
-
767
- ```ts
768
- // Stage 1: Monolithic function (hard to read/maintain)
769
- async function seedDatabase(signal: AbortSignal): Promise<void> {
770
- // 230+ lines of:
771
- // - Database connection logic
772
- // - Table discovery and filtering
773
- // - Complex column mapping per table
774
- // - Data streaming with error handling
775
- // - Sequence resetting logic
776
- // - Supabase user creation with tracking
777
- // - Transaction management
778
- // - Progress reporting
779
- }
780
-
781
- // Stage 2: Over-abstracted (too many trivial helpers)
782
- function parseSequenceName(name: string): string { return name.replace(/_id_seq$/, ""); }
783
- function findCommonColumns(source: string[], target: string[]): string[] { return source.filter(col => target.includes(col)); }
784
- async function getTableColumns(client: PoolClient, table: string): Promise<string[]> { /* Simple query wrapper */ return []; }
785
-
786
- // Stage 3: Balanced decomposition (extract meaningful operations)
787
- async function syncTableData(
788
- sourceClient: PoolClient,
789
- targetClient: PoolClient,
790
- table: string,
791
- signal: AbortSignal,
792
- ): Promise<{ skipped: boolean }> {
793
- // 40+ lines of complex column mapping, data streaming, error handling
794
- return { skipped: false };
795
- }
796
-
797
- async function resetSequences(targetClient: PoolClient): Promise<void> {
798
- // Meaningful database operation
799
- const tableName = seq.sequence_name.replace(/_id_seq$/, ""); // Inline simple operations
800
- }
801
-
802
- async function createSupabaseUsers(targetClient: PoolClient): Promise<void> {
803
- // Distinct business operation for user management
804
- }
805
-
806
- async function seedFromStaging(signal: AbortSignal): Promise<void> {
807
- // Clean orchestration function
808
- // - Database setup
809
- // - Transaction management
810
- // - Calling business operations
811
- // - Error handling
812
- }
813
- ```
814
-
815
- ### 9.2 Refactoring & Anti-Patterns
816
-
817
- **Why:** Keep code obvious by iterating in stages, avoiding abstraction or optimization until the third time you feel the pain.
818
-
819
- **Do:**
820
-
821
- - Start with the direct implementation; extract helpers only when they encapsulate meaningful business logic.
822
- - Duplicate logic on the second occurrence, then factor a helper on the third.
823
- - Keep related logic together; prefer pragmatic refactors over scattering files across faux layers.
824
-
825
- **Avoid:**
826
-
827
- - Abstractions invented after a single use (e.g., `AbstractDataProcessor` classes).
828
- - Clever function composition helpers when sequential code is clearer.
829
- - Premature caches, memoization, or micro-optimizations without evidence.
830
- - Dynamic property access that defeats type safety.