@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.
- package/README.md +3 -3
- package/dist/.vite/manifest.json +1111 -0
- package/dist/assets/_basePickBy-BMMA4Tou.js +1 -0
- package/dist/assets/_baseUniq-D40qku1I.js +1 -0
- package/dist/assets/arc-Ckg65iy8.js +1 -0
- package/dist/assets/architecture-YZFGNWBL-Dv3EY0zV.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-DQnkwSaB.js +36 -0
- package/dist/assets/array-Bjz-wYpJ.js +1 -0
- package/dist/assets/blockDiagram-DXYQGD6D-UB6_S1lm.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-sn3k2GND.js +10 -0
- package/dist/assets/channel-D9wPw2fQ.js +1 -0
- package/dist/assets/chunk-2KRD3SAO-DaFfaCGO.js +1 -0
- package/dist/assets/chunk-336JU56O-C8siO5Of.js +2 -0
- package/dist/assets/chunk-426QAEUC-BB478m3j.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-DRuTD7x5.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-_l6jvVAY.js +206 -0
- package/dist/assets/chunk-55IACEB6-BExiaAoD.js +1 -0
- package/dist/assets/chunk-5FUZZQ4R-HOSFTxuG.js +62 -0
- package/dist/assets/chunk-5PVQY5BW-BRVNNRAX.js +2 -0
- package/dist/assets/chunk-67CJDMHE-DMt8LNEX.js +1 -0
- package/dist/assets/chunk-7N4EOEYR-CzLGefVf.js +1 -0
- package/dist/assets/chunk-AA7GKIK3-B6GFAk4U.js +1 -0
- package/dist/assets/chunk-BSJP7CBP-BK29yehL.js +1 -0
- package/dist/assets/chunk-CIAEETIT-D7hBXImP.js +1 -0
- package/dist/assets/chunk-Dlc7tRH4.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-CSvKh9DT.js +1 -0
- package/dist/assets/chunk-ENJZ2VHE-QApb5cYr.js +10 -0
- package/dist/assets/chunk-FMBD7UC4-2FWyCCAV.js +15 -0
- package/dist/assets/chunk-FOC6F5B3-DKFHrt4K.js +1 -0
- package/dist/assets/chunk-ICPOFSXX-agBjBxsW.js +122 -0
- package/dist/assets/chunk-K5T4RW27-D51O7IkG.js +94 -0
- package/dist/assets/chunk-KGLVRYIC-DMHSCH4T.js +1 -0
- package/dist/assets/chunk-LIHQZDEY-C2aANxt9.js +1 -0
- package/dist/assets/chunk-ORNJ4GCN-Db_37NRX.js +1 -0
- package/dist/assets/chunk-OYMX7WX6-HGUtT2Q9.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-8Lcg9gti.js +1 -0
- package/dist/assets/chunk-U2HBQHQK-BFYYQeuC.js +70 -0
- package/dist/assets/chunk-X2U36JSP-p8ehTP6s.js +1 -0
- package/dist/assets/chunk-XPW4576I-Bqbompq4.js +32 -0
- package/dist/assets/chunk-YZCP3GAM-HIMez9pG.js +1 -0
- package/dist/assets/chunk-ZZ45TVLE-DRIE_0bu.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-BawhEeUl.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CLNjgH9n.js +1 -0
- package/dist/assets/clone-BBjvuERA.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-q90QeGKv.js +1 -0
- package/dist/assets/cytoscape.esm-BfXff3fb.js +321 -0
- package/dist/assets/dagre-Dxbob2Lr.js +1 -0
- package/dist/assets/dagre-KV5264BT-BuvpNxMw.js +4 -0
- package/dist/assets/defaultLocale-BwmRmqJp.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-DQLsxwwt.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Jv9Eefw4.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-D-0YgNhU.js +43 -0
- package/dist/assets/diagram-TYMM5635-BHwO7zQG.js +24 -0
- package/dist/assets/dist-BNz65Ibc.js +1 -0
- package/dist/assets/erDiagram-SMLLAGMA-BjZGGBJz.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-CFbFUm_m.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-CXk4TcBi.js +292 -0
- package/dist/assets/gitGraph-7Q5UKJZL-BGFRt2qs.js +1 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-C8yZOxjo.js +106 -0
- package/dist/assets/graphlib-DGcD9J2L.js +1 -0
- package/dist/assets/index-D-m0LiFI.js +14 -0
- package/dist/assets/index-DANHO6J0.css +2 -0
- package/dist/assets/info-OMHHGYJF-DI6-Z9vh.js +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-p-PXDra2.js +2 -0
- package/dist/assets/init-TPm5RB77.js +1 -0
- package/dist/assets/isArrayLikeObject-69BLnVNM.js +1 -0
- package/dist/assets/isEmpty-DUS28g5f.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-BrIoEvtb.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-aZpvKa9g.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-CoOAY9ji.js +89 -0
- package/dist/assets/katex-5SGEXwpi.js +261 -0
- package/dist/assets/line-4MF1lR4d.js +1 -0
- package/dist/assets/linear-CXMqTN8N.js +1 -0
- package/dist/assets/mermaid-config-C8a4L22x.js +1 -0
- package/dist/assets/mermaid-parser.core-DREsY2u4.js +4 -0
- package/dist/assets/mermaid.core-8ysLpTJi.js +11 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-CsqUJCMn.js +96 -0
- package/dist/assets/ordinal-D7l-8DAO.js +1 -0
- package/dist/assets/packet-4T2RLAQJ-DidW3JFc.js +1 -0
- package/dist/assets/path-BVpCanzE.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-Bff2e5hg.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-k0Br4NDS.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-Be9oCSza.js +7 -0
- package/dist/assets/radar-PYXPWWZC-CsdZBH3M.js +1 -0
- package/dist/assets/requirementDiagram-MS252O5E-8ECT7dEs.js +84 -0
- package/dist/assets/rough.esm-BoTisKeL.js +1 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-CoKpeJJ0.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-BTT2fFxG.js +157 -0
- package/dist/assets/src-CrmkjRpa.js +1 -0
- package/dist/assets/stateDiagram-FHFEXIEX-CIF47NYe.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Cy1rmPfG.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-Bes4B58n.js +120 -0
- package/dist/assets/treeView-SZITEDCU-DPKseaET.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-DH-7GZe_.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-3wx2huKk.js +34 -0
- package/dist/assets/wardley-RL74JXVD-AgyXyBN5.js +1 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DzViT1Yx.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-BO_dbU0r.js +7 -0
- package/{index.html → dist/index.html} +2 -1
- package/dist/index.js +2625 -0
- package/package.json +11 -1
- package/.agents/skills/remotion-best-practices/SKILL.md +0 -61
- package/.agents/skills/remotion-best-practices/rules/3d.md +0 -86
- package/.agents/skills/remotion-best-practices/rules/animations.md +0 -27
- package/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +0 -178
- package/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +0 -100
- package/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +0 -108
- package/.agents/skills/remotion-best-practices/rules/assets.md +0 -78
- package/.agents/skills/remotion-best-practices/rules/audio-visualization.md +0 -198
- package/.agents/skills/remotion-best-practices/rules/audio.md +0 -169
- package/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +0 -134
- package/.agents/skills/remotion-best-practices/rules/can-decode.md +0 -75
- package/.agents/skills/remotion-best-practices/rules/charts.md +0 -120
- package/.agents/skills/remotion-best-practices/rules/compositions.md +0 -154
- package/.agents/skills/remotion-best-practices/rules/display-captions.md +0 -184
- package/.agents/skills/remotion-best-practices/rules/extract-frames.md +0 -229
- package/.agents/skills/remotion-best-practices/rules/ffmpeg.md +0 -38
- package/.agents/skills/remotion-best-practices/rules/fonts.md +0 -152
- package/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +0 -58
- package/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +0 -68
- package/.agents/skills/remotion-best-practices/rules/get-video-duration.md +0 -60
- package/.agents/skills/remotion-best-practices/rules/gifs.md +0 -141
- package/.agents/skills/remotion-best-practices/rules/images.md +0 -134
- package/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +0 -69
- package/.agents/skills/remotion-best-practices/rules/light-leaks.md +0 -73
- package/.agents/skills/remotion-best-practices/rules/lottie.md +0 -70
- package/.agents/skills/remotion-best-practices/rules/maps.md +0 -412
- package/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +0 -34
- package/.agents/skills/remotion-best-practices/rules/measuring-text.md +0 -140
- package/.agents/skills/remotion-best-practices/rules/parameters.md +0 -109
- package/.agents/skills/remotion-best-practices/rules/sequencing.md +0 -118
- package/.agents/skills/remotion-best-practices/rules/sfx.md +0 -26
- package/.agents/skills/remotion-best-practices/rules/subtitles.md +0 -36
- package/.agents/skills/remotion-best-practices/rules/tailwind.md +0 -11
- package/.agents/skills/remotion-best-practices/rules/text-animations.md +0 -20
- package/.agents/skills/remotion-best-practices/rules/timing.md +0 -179
- package/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +0 -70
- package/.agents/skills/remotion-best-practices/rules/transitions.md +0 -197
- package/.agents/skills/remotion-best-practices/rules/transparent-videos.md +0 -106
- package/.agents/skills/remotion-best-practices/rules/trimming.md +0 -51
- package/.agents/skills/remotion-best-practices/rules/videos.md +0 -171
- package/.agents/skills/remotion-best-practices/rules/voiceover.md +0 -99
- package/.agents/skills/simple/SKILL.md +0 -52
- package/.agents/skills/vercel-react-best-practices/AGENTS.md +0 -3254
- package/.agents/skills/vercel-react-best-practices/README.md +0 -123
- package/.agents/skills/vercel-react-best-practices/SKILL.md +0 -141
- package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +0 -42
- package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -39
- package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
- package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
- package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -51
- package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
- package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
- package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
- package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
- package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
- package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
- package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
- package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -107
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
- package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
- package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
- package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
- package/.agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +0 -60
- package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
- package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
- package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
- package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
- package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +0 -30
- package/.agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +0 -85
- package/.agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +0 -68
- package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +0 -75
- package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +0 -40
- package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
- package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +0 -38
- package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
- package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +0 -45
- package/.agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +0 -82
- package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +0 -35
- package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
- package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +0 -73
- package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +0 -96
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
- package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
- package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +0 -65
- package/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +0 -142
- package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
- package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
- package/.claude/CLAUDE.md +0 -184
- package/.claude/commands/review.md +0 -120
- package/.claude/commands/sync-docs.md +0 -71
- package/.claude/roadmap.md +0 -121
- package/.claude/rules/style-guide.md +0 -830
- package/.claude/settings.json +0 -18
- package/.claude/user-stories.md +0 -333
- package/AGENTS.md +0 -68
- package/Makefile +0 -32
- package/biome.json +0 -79
- package/bun.lock +0 -854
- package/bunfig.toml +0 -2
- package/docs/design.md +0 -563
- package/docs/perf-baseline.md +0 -130
- package/docs/plans/2026-03-13-client-mode-design.md +0 -86
- package/docs/plans/2026-03-13-client-mode-plan.md +0 -605
- package/docs/plans/2026-03-13-keyboard-shortcuts-design.md +0 -129
- package/docs/plans/2026-03-13-keyboard-shortcuts-plan.md +0 -1471
- package/docs/plans/2026-03-13-multi-document-design.md +0 -183
- package/docs/plans/2026-03-13-performance-benchmarks-design.md +0 -121
- package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +0 -1176
- package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +0 -284
- package/e2e/comments.spec.ts +0 -81
- package/e2e/document-load.spec.ts +0 -32
- package/e2e/export.spec.ts +0 -58
- package/e2e/fixtures/sample.md +0 -7
- package/e2e/perf/add-comment.spec.ts +0 -116
- package/e2e/perf/fixtures/generate.ts +0 -327
- package/e2e/perf/initial-load.spec.ts +0 -49
- package/e2e/perf/perf.setup.ts +0 -23
- package/e2e/perf/perf.teardown.ts +0 -9
- package/e2e/perf/screenshot-final.png +0 -0
- package/e2e/perf/scroll.spec.ts +0 -39
- package/e2e/perf/tab-switch.spec.ts +0 -69
- package/e2e/perf/text-selection.spec.ts +0 -119
- package/e2e/perf/utils/metrics.ts +0 -350
- package/e2e/perf/utils/perf-cli.ts +0 -86
- package/e2e/persistence-file.spec.ts +0 -357
- package/e2e/utils/cli.ts +0 -84
- package/e2e/utils/selection.ts +0 -79
- package/go/cmd/readit/main.go +0 -416
- package/go/go.mod +0 -20
- package/go/go.sum +0 -41
- package/go/internal/server/anchor.go +0 -302
- package/go/internal/server/anchor_test.go +0 -111
- package/go/internal/server/comments.go +0 -390
- package/go/internal/server/documents.go +0 -113
- package/go/internal/server/embed.go +0 -17
- package/go/internal/server/headings.go +0 -33
- package/go/internal/server/headings_test.go +0 -75
- package/go/internal/server/htmltext.go +0 -123
- package/go/internal/server/markdown.go +0 -157
- package/go/internal/server/markdown_bench_test.go +0 -42
- package/go/internal/server/markdown_test.go +0 -79
- package/go/internal/server/server.go +0 -453
- package/go/internal/server/server_bench_test.go +0 -122
- package/go/internal/server/settings.go +0 -110
- package/go/internal/server/sse.go +0 -140
- package/go/internal/server/storage.go +0 -275
- package/go/internal/server/storage_test.go +0 -152
- package/go/internal/server/template.go +0 -66
- package/go/internal/server/types.go +0 -101
- package/go/internal/server/watcher.go +0 -74
- package/lefthook.yml +0 -8
- package/nvim-readit/lua/readit/health.lua +0 -64
- package/nvim-readit/lua/readit/init.lua +0 -463
- package/nvim-readit/plugin/readit.lua +0 -19
- package/playwright.config.ts +0 -34
- package/skills-lock.json +0 -20
- package/src/App.svelte +0 -890
- package/src/cli.ts +0 -881
- package/src/components/ActionsMenu.svelte +0 -95
- package/src/components/CommentBadge.svelte +0 -67
- package/src/components/CommentErrorBanner.svelte +0 -33
- package/src/components/CommentInput.svelte +0 -75
- package/src/components/CommentListItem.svelte +0 -95
- package/src/components/CommentManager.svelte +0 -129
- package/src/components/CommentNav.svelte +0 -109
- package/src/components/DocumentViewer.svelte +0 -233
- package/src/components/FloatingComment.svelte +0 -107
- package/src/components/Header.svelte +0 -76
- package/src/components/InlineEditor.svelte +0 -72
- package/src/components/MarginNote.svelte +0 -167
- package/src/components/MarginNotesContainer.svelte +0 -33
- package/src/components/MermaidEnhancer.svelte +0 -218
- package/src/components/MermaidModal.svelte +0 -67
- package/src/components/RawModal.svelte +0 -126
- package/src/components/ReanchorConfirm.svelte +0 -30
- package/src/components/SettingsModal.svelte +0 -220
- package/src/components/ShortcutCapture.svelte +0 -82
- package/src/components/ShortcutList.svelte +0 -145
- package/src/components/TabBar.svelte +0 -52
- package/src/components/TableOfContents.svelte +0 -125
- package/src/components/ui/ActionLink.svelte +0 -40
- package/src/components/ui/Button.svelte +0 -53
- package/src/components/ui/Dialog.svelte +0 -97
- package/src/components/ui/DropdownMenu.svelte +0 -85
- package/src/components/ui/DropdownMenuItem.svelte +0 -38
- package/src/components/ui/DropdownMenuSeparator.svelte +0 -11
- package/src/components/ui/Text.svelte +0 -42
- package/src/env.d.ts +0 -6
- package/src/index.css +0 -859
- package/src/lib/__fixtures__/bench-data.ts +0 -114
- package/src/lib/anchor.bench.ts +0 -91
- package/src/lib/anchor.test.ts +0 -527
- package/src/lib/anchor.ts +0 -381
- package/src/lib/comment-storage.bench.ts +0 -49
- package/src/lib/comment-storage.test.ts +0 -694
- package/src/lib/comment-storage.ts +0 -226
- package/src/lib/export.bench.ts +0 -21
- package/src/lib/export.ts +0 -36
- package/src/lib/fetch-or-throw.test.ts +0 -59
- package/src/lib/fetch-or-throw.ts +0 -12
- package/src/lib/headings.test.ts +0 -103
- package/src/lib/headings.ts +0 -44
- package/src/lib/highlight/core.test.ts +0 -93
- package/src/lib/highlight/dom.ts +0 -187
- package/src/lib/highlight/highlight-registry.ts +0 -221
- package/src/lib/highlight/highlight.bench.ts +0 -92
- package/src/lib/highlight/highlighter.ts +0 -247
- package/src/lib/highlight/resolver.ts +0 -38
- package/src/lib/highlight/types.ts +0 -17
- package/src/lib/html-text.test.ts +0 -162
- package/src/lib/html-text.ts +0 -161
- package/src/lib/i18n/en.ts +0 -124
- package/src/lib/i18n/index.ts +0 -3
- package/src/lib/i18n/ja.ts +0 -126
- package/src/lib/i18n/translations.ts +0 -27
- package/src/lib/i18n/types.ts +0 -130
- package/src/lib/key-lock.test.ts +0 -104
- package/src/lib/key-lock.ts +0 -23
- package/src/lib/margin-layout.bench.ts +0 -61
- package/src/lib/margin-layout.ts +0 -71
- package/src/lib/markdown-renderer.test.ts +0 -154
- package/src/lib/markdown-renderer.ts +0 -178
- package/src/lib/mermaid-config.ts +0 -38
- package/src/lib/mermaid-renderer.ts +0 -162
- package/src/lib/mermaid-worker.ts +0 -60
- package/src/lib/positions.ts +0 -157
- package/src/lib/shortcut-registry.ts +0 -244
- package/src/lib/utils.ts +0 -15
- package/src/main.ts +0 -16
- package/src/schema.ts +0 -92
- package/src/server.ts +0 -1216
- package/src/stores/app.svelte.ts +0 -231
- package/src/stores/locale.svelte.ts +0 -46
- package/src/stores/settings.svelte.ts +0 -90
- package/src/stores/shortcuts.svelte.ts +0 -104
- package/src/stores/ui.svelte.ts +0 -12
- package/src/template.ts +0 -104
- package/src/test-setup.ts +0 -48
- package/svelte.config.js +0 -5
- package/test.md +0 -74
- package/tsconfig.cli.json +0 -12
- package/tsconfig.json +0 -20
- package/vite.config.ts +0 -47
- package/vitest.config.ts +0 -15
- package/vscode-readit/.mcp.json +0 -7
- package/vscode-readit/.vscodeignore +0 -7
- package/vscode-readit/bun.lock +0 -78
- package/vscode-readit/icon.svg +0 -10
- package/vscode-readit/package.json +0 -110
- package/vscode-readit/src/extension.ts +0 -117
- package/vscode-readit/src/server-manager.ts +0 -272
- package/vscode-readit/src/webview-provider.ts +0 -204
- 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.
|