secondbrain-py 0.2.1__py3-none-any.whl

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 (273) hide show
  1. brain/__init__.py +0 -0
  2. brain/__main__.py +18 -0
  3. brain/_capture_command.py +445 -0
  4. brain/_compose.py +52 -0
  5. brain/activity.py +206 -0
  6. brain/ask.py +631 -0
  7. brain/audio.py +591 -0
  8. brain/backfill/__init__.py +12 -0
  9. brain/backfill/search_extras.py +141 -0
  10. brain/backfill/source_rows.py +101 -0
  11. brain/bin/__init__.py +1 -0
  12. brain/bin/_launcher.py +107 -0
  13. brain/bin/down.py +8 -0
  14. brain/bin/launchd.py +268 -0
  15. brain/bin/monitor.py +570 -0
  16. brain/bin/rebuild.py +8 -0
  17. brain/bin/status.py +8 -0
  18. brain/bin/up.py +8 -0
  19. brain/brief.py +272 -0
  20. brain/capture.py +49 -0
  21. brain/chat.py +293 -0
  22. brain/cli.py +9760 -0
  23. brain/cli_claude.py +81 -0
  24. brain/cli_connect.py +285 -0
  25. brain/cli_demo.py +266 -0
  26. brain/config.py +1949 -0
  27. brain/connect.py +925 -0
  28. brain/db.py +540 -0
  29. brain/demo/__init__.py +452 -0
  30. brain/demo/corpus/manifest.json +403 -0
  31. brain/demo/embedder.py +74 -0
  32. brain/durations.py +84 -0
  33. brain/edit_session.py +156 -0
  34. brain/editor.py +67 -0
  35. brain/elicit/__init__.py +16 -0
  36. brain/elicit/detectors.py +250 -0
  37. brain/elicit/drafter.py +70 -0
  38. brain/elicit/queue.py +220 -0
  39. brain/elicit/schema.py +48 -0
  40. brain/elicit/session.py +445 -0
  41. brain/embedding_targets.py +54 -0
  42. brain/embeddings.py +424 -0
  43. brain/enrichment.py +808 -0
  44. brain/errors.py +357 -0
  45. brain/eval/__init__.py +129 -0
  46. brain/eval/answer_eval.py +281 -0
  47. brain/eval/baseline.py +265 -0
  48. brain/eval/concept_extraction.py +378 -0
  49. brain/eval/corpus.py +152 -0
  50. brain/eval/errors.py +19 -0
  51. brain/eval/graph_baseline.py +226 -0
  52. brain/eval/graph_retrieval.py +202 -0
  53. brain/eval/graph_runner.py +319 -0
  54. brain/eval/metrics.py +101 -0
  55. brain/eval/runner.py +223 -0
  56. brain/format.py +783 -0
  57. brain/gaps.py +390 -0
  58. brain/graph_rag/__init__.py +94 -0
  59. brain/graph_rag/_retrieval_common.py +113 -0
  60. brain/graph_rag/aggregates.py +303 -0
  61. brain/graph_rag/aliases/__init__.py +583 -0
  62. brain/graph_rag/backends/__init__.py +10 -0
  63. brain/graph_rag/backends/_age_helpers.py +473 -0
  64. brain/graph_rag/backends/age.py +782 -0
  65. brain/graph_rag/backends/base.py +272 -0
  66. brain/graph_rag/build.py +344 -0
  67. brain/graph_rag/communities.py +644 -0
  68. brain/graph_rag/communities_summary.py +437 -0
  69. brain/graph_rag/concepts.py +202 -0
  70. brain/graph_rag/cooccur.py +193 -0
  71. brain/graph_rag/cross_type.py +312 -0
  72. brain/graph_rag/extract.py +885 -0
  73. brain/graph_rag/fuse.py +371 -0
  74. brain/graph_rag/global_.py +412 -0
  75. brain/graph_rag/grouping.py +372 -0
  76. brain/graph_rag/person_resolver.py +167 -0
  77. brain/graph_rag/reconcile.py +792 -0
  78. brain/graph_rag/relational.py +353 -0
  79. brain/graph_rag/retrieve.py +526 -0
  80. brain/graph_rag/router.py +288 -0
  81. brain/graph_rag/schema.py +320 -0
  82. brain/graph_rag/sync.py +237 -0
  83. brain/graph_rag/tenancy.py +43 -0
  84. brain/graph_rag/themes.py +501 -0
  85. brain/graph_rag/weighting.py +202 -0
  86. brain/ingest/__init__.py +1926 -0
  87. brain/ingest/chunker.py +249 -0
  88. brain/ingest/docx.py +40 -0
  89. brain/ingest/gmail.py +621 -0
  90. brain/ingest/markdown.py +37 -0
  91. brain/ingest/pdf.py +61 -0
  92. brain/ingest/stdin.py +22 -0
  93. brain/ingest/sub_tokens.py +91 -0
  94. brain/ingest/text.py +16 -0
  95. brain/interactions.py +205 -0
  96. brain/maintenance.py +355 -0
  97. brain/mcp_server.py +3405 -0
  98. brain/migrations/001_init.sql +43 -0
  99. brain/migrations/002_qwen3_embedding.sql +17 -0
  100. brain/migrations/003_vault_model.sql +41 -0
  101. brain/migrations/004_relax_content_hash_uniqueness.sql +18 -0
  102. brain/migrations/005_derived_links.sql +67 -0
  103. brain/migrations/006_dedup_file_by_source_path.sql +25 -0
  104. brain/migrations/007_email_thread_and_draft.sql +15 -0
  105. brain/migrations/008_gmail_thread_unique.sql +11 -0
  106. brain/migrations/009_chunks_weighted_tsv.sql +28 -0
  107. brain/migrations/010_interactions.sql +30 -0
  108. brain/migrations/011_documents_summary.sql +23 -0
  109. brain/migrations/012_graphrag.sql +171 -0
  110. brain/migrations/013_graphrag_communities.sql +125 -0
  111. brain/migrations/014_graphrag_community_summary_hash.sql +33 -0
  112. brain/migrations/015_interactions_graph_targets.sql +89 -0
  113. brain/migrations/016_index_hygiene.sql +61 -0
  114. brain/migrations/017_elicit.sql +30 -0
  115. brain/migrations/018_review_gap_signal_kinds.sql +40 -0
  116. brain/migrations/019_search_queries.sql +35 -0
  117. brain/migrations/020_link_suggestions.sql +40 -0
  118. brain/migrations/021_timeline_doc_date.sql +34 -0
  119. brain/migrations/022_link_suggestions_undirected.sql +84 -0
  120. brain/migrations/023_search_queries_fts_count.sql +28 -0
  121. brain/quartz_overrides/__init__.py +8 -0
  122. brain/quartz_overrides/quartz/bootstrap-cli.mjs +65 -0
  123. brain/quartz_overrides/quartz/build.ts +568 -0
  124. brain/quartz_overrides/quartz/cli/args.js +152 -0
  125. brain/quartz_overrides/quartz/cli/build_partial_handler.js +544 -0
  126. brain/quartz_overrides/quartz/cli/handlers.js +636 -0
  127. brain/quartz_overrides/quartz/components/CommandPalette.tsx +172 -0
  128. brain/quartz_overrides/quartz/components/Explorer.tsx +198 -0
  129. brain/quartz_overrides/quartz/components/Footer.tsx +27 -0
  130. brain/quartz_overrides/quartz/components/Graph.tsx +468 -0
  131. brain/quartz_overrides/quartz/components/PageTitle.tsx +72 -0
  132. brain/quartz_overrides/quartz/components/RelatedDocs.tsx +38 -0
  133. brain/quartz_overrides/quartz/components/Search.tsx +161 -0
  134. brain/quartz_overrides/quartz/components/SummaryLede.tsx +72 -0
  135. brain/quartz_overrides/quartz/components/index.ts +92 -0
  136. brain/quartz_overrides/quartz/components/pages/TagContent.tsx +272 -0
  137. brain/quartz_overrides/quartz/components/scripts/commandPalette.inline.ts +665 -0
  138. brain/quartz_overrides/quartz/components/scripts/explorer.inline.ts +768 -0
  139. brain/quartz_overrides/quartz/components/scripts/graph.inline.ts +2302 -0
  140. brain/quartz_overrides/quartz/components/scripts/relatedDocs.inline.ts +163 -0
  141. brain/quartz_overrides/quartz/components/scripts/search.inline.ts +1011 -0
  142. brain/quartz_overrides/quartz/plugins/emitters/contentIndex.ts +546 -0
  143. brain/quartz_overrides/quartz/plugins/transformers/codeCopy.ts +94 -0
  144. brain/quartz_overrides/quartz/plugins/transformers/derivedFenceMark.ts +302 -0
  145. brain/quartz_overrides/quartz/plugins/transformers/emailThread.ts +148 -0
  146. brain/quartz_overrides/quartz/plugins/transformers/emptyDoorFilter.ts +213 -0
  147. brain/quartz_overrides/quartz/plugins/transformers/index.ts +114 -0
  148. brain/quartz_overrides/quartz/plugins/transformers/linkKindMark.ts +205 -0
  149. brain/quartz_overrides/quartz/plugins/transformers/linkSourceTag.ts +104 -0
  150. brain/quartz_overrides/quartz/plugins/transformers/relativeDate.ts +100 -0
  151. brain/quartz_overrides/quartz/plugins/transformers/reloadSignal.ts +131 -0
  152. brain/quartz_overrides/quartz/processors/parse.ts +371 -0
  153. brain/quartz_overrides/quartz/processors/parser_cache.ts +78 -0
  154. brain/quartz_overrides/quartz/static/brain-logo-dark.png +0 -0
  155. brain/quartz_overrides/quartz/static/brain-logo-light.png +0 -0
  156. brain/quartz_overrides/quartz/static/codeCopy.js +196 -0
  157. brain/quartz_overrides/quartz/static/emailThread.js +334 -0
  158. brain/quartz_overrides/quartz/static/favicon.ico +0 -0
  159. brain/quartz_overrides/quartz/static/icon.png +0 -0
  160. brain/quartz_overrides/quartz/static/linkSourceTag.js +104 -0
  161. brain/quartz_overrides/quartz/static/relativeDate.js +142 -0
  162. brain/quartz_overrides/quartz/static/reload.js +168 -0
  163. brain/quartz_overrides/quartz/styles/brain/_article.scss +252 -0
  164. brain/quartz_overrides/quartz/styles/brain/_atmosphere.scss +113 -0
  165. brain/quartz_overrides/quartz/styles/brain/_callouts.scss +180 -0
  166. brain/quartz_overrides/quartz/styles/brain/_cmdk.scss +7 -0
  167. brain/quartz_overrides/quartz/styles/brain/_code.scss +208 -0
  168. brain/quartz_overrides/quartz/styles/brain/_command_palette.scss +369 -0
  169. brain/quartz_overrides/quartz/styles/brain/_email_thread.scss +228 -0
  170. brain/quartz_overrides/quartz/styles/brain/_explorer.scss +142 -0
  171. brain/quartz_overrides/quartz/styles/brain/_home.scss +182 -0
  172. brain/quartz_overrides/quartz/styles/brain/_links.scss +322 -0
  173. brain/quartz_overrides/quartz/styles/brain/_marginalia.scss +117 -0
  174. brain/quartz_overrides/quartz/styles/brain/_motion.scss +175 -0
  175. brain/quartz_overrides/quartz/styles/brain/_people_hub.scss +100 -0
  176. brain/quartz_overrides/quartz/styles/brain/_related_docs.scss +137 -0
  177. brain/quartz_overrides/quartz/styles/brain/_search.scss +252 -0
  178. brain/quartz_overrides/quartz/styles/brain/_sidebar.scss +468 -0
  179. brain/quartz_overrides/quartz/styles/brain/_summary_lede.scss +56 -0
  180. brain/quartz_overrides/quartz/styles/brain/_surface.scss +43 -0
  181. brain/quartz_overrides/quartz/styles/brain/_tag_content.scss +118 -0
  182. brain/quartz_overrides/quartz/styles/brain/_tokens.scss +197 -0
  183. brain/quartz_overrides/quartz/styles/brain/_typography.scss +92 -0
  184. brain/quartz_overrides/quartz/styles/custom.scss +89 -0
  185. brain/quartz_overrides/quartz/styles/graph.scss +505 -0
  186. brain/quartz_overrides/quartz/util/ctx.ts +92 -0
  187. brain/quartz_overrides/quartz/util/fastpath_manifest.ts +608 -0
  188. brain/quartz_overrides/quartz/util/path.ts +358 -0
  189. brain/quartz_overrides/quartz/util/sourceIcons.ts +55 -0
  190. brain/quartz_overrides/quartz.config.ts +270 -0
  191. brain/quartz_overrides/quartz.layout.ts +314 -0
  192. brain/queries.py +1188 -0
  193. brain/rank_fusion.py +8 -0
  194. brain/resurface.py +210 -0
  195. brain/review/__init__.py +26 -0
  196. brain/review/emit.py +27 -0
  197. brain/review/queries.py +436 -0
  198. brain/review/render.py +196 -0
  199. brain/review/scans.py +355 -0
  200. brain/review/weekly.py +413 -0
  201. brain/search.py +704 -0
  202. brain/set_similarity.py +15 -0
  203. brain/setup.py +1205 -0
  204. brain/tags.py +56 -0
  205. brain/templates/Caddyfile.j2 +9 -0
  206. brain/templates/__init__.py +1 -0
  207. brain/templates/bin/__init__.py +1 -0
  208. brain/templates/bin/_brain-brief-fg.sh +25 -0
  209. brain/templates/bin/_brain-build-fg.sh +53 -0
  210. brain/templates/bin/_brain-watcher-fg.sh +65 -0
  211. brain/templates/bin/brain-down.sh +89 -0
  212. brain/templates/bin/brain-status.sh +83 -0
  213. brain/templates/bin/brain-up.sh +221 -0
  214. brain/templates/docker/age/Dockerfile +79 -0
  215. brain/templates/docker-compose.stock.yml.j2 +26 -0
  216. brain/templates/docker-compose.yml.j2 +34 -0
  217. brain/templates/env.example +190 -0
  218. brain/templates/launchd/__init__.py +1 -0
  219. brain/templates/launchd/com.brain.brief.plist.j2 +45 -0
  220. brain/templates/launchd/com.brain.build.plist.j2 +46 -0
  221. brain/templates/launchd/com.brain.watcher.plist.j2 +46 -0
  222. brain/templates/skill/SKILL.md +63 -0
  223. brain/templates/skill/__init__.py +1 -0
  224. brain/timeline.py +834 -0
  225. brain/todo.py +124 -0
  226. brain/uninstall.py +185 -0
  227. brain/vault/__init__.py +115 -0
  228. brain/vault/_atomic.py +25 -0
  229. brain/vault/daily_index.py +228 -0
  230. brain/vault/derived_links/__init__.py +50 -0
  231. brain/vault/derived_links/directory.py +683 -0
  232. brain/vault/derived_links/fence.py +408 -0
  233. brain/vault/derived_links/gws.py +64 -0
  234. brain/vault/derived_links/participants.py +143 -0
  235. brain/vault/derived_links/pass_runner.py +362 -0
  236. brain/vault/derived_links/rules.py +137 -0
  237. brain/vault/export.py +683 -0
  238. brain/vault/frontmatter.py +165 -0
  239. brain/vault/graph.py +620 -0
  240. brain/vault/graph_format.py +388 -0
  241. brain/vault/link_rewrite.py +235 -0
  242. brain/vault/links.py +260 -0
  243. brain/vault/note_builder.py +211 -0
  244. brain/vault/paths.py +55 -0
  245. brain/vault/quartz_overlay.py +236 -0
  246. brain/vault/rename.py +591 -0
  247. brain/vault/resolver.py +304 -0
  248. brain/vault/slug.py +127 -0
  249. brain/vault/sync.py +1513 -0
  250. brain/vault/sync_summaries.py +264 -0
  251. brain/vault/templates.py +145 -0
  252. brain/vault/watch.py +1052 -0
  253. brain/wiki/__init__.py +6 -0
  254. brain/wiki/_github_slugger.py +76 -0
  255. brain/wiki/_person_name.py +314 -0
  256. brain/wiki/build_homepage.py +541 -0
  257. brain/wiki/build_partial.py +273 -0
  258. brain/wiki/build_people.py +934 -0
  259. brain/wiki/build_related.py +758 -0
  260. brain/wiki/build_swap.py +585 -0
  261. brain/wiki/build_watcher.py +975 -0
  262. brain/wiki/edit_classifier.py +215 -0
  263. brain/wiki/errors.py +10 -0
  264. brain/wiki/fastpath_manifest.py +475 -0
  265. brain/wiki/fastpath_state.py +174 -0
  266. brain/wiki/install.py +296 -0
  267. brain/wiki/slug.py +111 -0
  268. secondbrain_py-0.2.1.dist-info/METADATA +195 -0
  269. secondbrain_py-0.2.1.dist-info/RECORD +273 -0
  270. secondbrain_py-0.2.1.dist-info/WHEEL +5 -0
  271. secondbrain_py-0.2.1.dist-info/entry_points.txt +11 -0
  272. secondbrain_py-0.2.1.dist-info/licenses/LICENSE +21 -0
  273. secondbrain_py-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,114 @@
1
+ // Brain wiki — transformer barrel re-export with brain extensions wired in.
2
+ //
3
+ // PARSER CACHE CONTRACT — READ BEFORE ADDING A TRANSFORMER:
4
+ //
5
+ // MDAST transformers in this directory MUST be pure functions of
6
+ // (file bytes, slug). "Pure" means: given the same file bytes and the
7
+ // same slug, a transformer MUST produce the same output AST every time,
8
+ // with no dependence on sibling files, the build graph, or any mutable
9
+ // global state.
10
+ //
11
+ // Why this matters: the parser cache in
12
+ // `quartz_overrides/quartz/processors/parser_cache.ts` keys each
13
+ // cached [MDAST, file.data] entry on:
14
+ //
15
+ // sha256(CACHE_VERSION as 4 bytes || slug as UTF-8 || file bytes)
16
+ //
17
+ // If a transformer reads data from other files, a database, or any
18
+ // external source at the parse stage, a cache hit for an unchanged file
19
+ // would return stale output even though a dependency changed.
20
+ //
21
+ // Cross-file context (backlinks, contentIndex, related-docs, derived
22
+ // edges) belongs in the EMITTER phase, which runs on the in-memory
23
+ // ProcessedContent[] collection AFTER parseMarkdown returns. No
24
+ // transformer here may reach across files at parse time.
25
+ //
26
+ // Version bumping: if you change a transformer in a way that alters its
27
+ // output for the same input (e.g. new data attributes, changed tree
28
+ // structure), bump CACHE_VERSION in parser_cache.ts in the SAME COMMIT
29
+ // as the transformer change. A version bump invalidates all existing
30
+ // cache entries globally (by changing every key) so the next build
31
+ // re-derives correct output for every file.
32
+ //
33
+ // Audit checklist for new transformers:
34
+ // [x] Pure function of (file bytes, slug) — no cross-file reads
35
+ // [x] No mutable module-level state that varies across files
36
+ // [x] If output format changes: CACHE_VERSION bumped in same commit
37
+ //
38
+ // This file is a TEMPLATE. It is installed at
39
+ // `<vault>/.quartz/quartz/plugins/transformers/index.ts` by `brain
40
+ // vault render --overlay`, OVERWRITING the stock Quartz barrel. The
41
+ // overlay is a 1:1 file copy (`src/brain/vault/quartz_overlay.py`),
42
+ // so this is the canonical seam for ensuring the brain-extension
43
+ // transformer files in this directory get re-exported under the
44
+ // `Plugin.*` namespace consumed by `quartz.config.ts`.
45
+ //
46
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
47
+ // version adds a new stock transformer, append its export below; the
48
+ // ones that already exist are sourced verbatim from
49
+ // https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/index.ts
50
+ // and the brain extensions are appended at the bottom in their own
51
+ // block.
52
+ //
53
+ // brain: stock Quartz transformer exports — keep in lock-step with
54
+ // the upstream `index.ts` linked above. If you upgrade Quartz, diff
55
+ // upstream against this block and apply additions.
56
+ export { FrontMatter } from "./frontmatter"
57
+ export { GitHubFlavoredMarkdown } from "./gfm"
58
+ export { Citations } from "./citations"
59
+ export { CreatedModifiedDate } from "./lastmod"
60
+ export { Latex } from "./latex"
61
+ export { Description } from "./description"
62
+ export { CrawlLinks } from "./links"
63
+ export { ObsidianFlavoredMarkdown } from "./ofm"
64
+ export { OxHugoFlavouredMarkdown } from "./oxhugofm"
65
+ export { SyntaxHighlighting } from "./syntax"
66
+ export { TableOfContents } from "./toc"
67
+ export { HardLineBreaks } from "./linebreaks"
68
+ export { RoamFlavoredMarkdown } from "./roam"
69
+
70
+ // brain-extension: brain-only transformers added by the overlay. Each
71
+ // is documented in its own file's top-of-file comment.
72
+ // * DerivedFenceMark — stamps Phase D fence links with
73
+ // `data-brain-derived` so the contentIndex emitter and the Lane B
74
+ // link classifier can recognise them as evidence edges.
75
+ // * ReloadSignal — injects the polling reload watcher when
76
+ // `BRAIN_WIKI_RELOAD=1` (dev path); see the blue-green serve plan.
77
+ // * LinkKindMark — Lane B redesign — stamps every `<a>` with
78
+ // `data-brain-link-kind` so the redesigned link styles can
79
+ // distinguish wiki / external / tag / ingested / derived kinds.
80
+ // * LinkSourceTag — Lane B redesign — injects
81
+ // `/static/linkSourceTag.js` to add `data-brain-source` to
82
+ // ingested links at runtime (source identity is in the URL path
83
+ // but extraction at build-time would require a doc lookup).
84
+ // * CodeCopy — Lane C redesign — injects
85
+ // `/static/codeCopy.js` to add a brain-themed copy button to
86
+ // every `<pre>` in the article (supersedes stock Quartz's
87
+ // `.clipboard-button`, which `_code.scss` hides) and to lift
88
+ // the `data-language` attribute from inner `<code>` to outer
89
+ // `<pre>` so the CSS-only language label can render via
90
+ // `attr()`.
91
+ export { DerivedFenceMark } from "./derivedFenceMark"
92
+ export { ReloadSignal } from "./reloadSignal"
93
+ export { LinkKindMark } from "./linkKindMark"
94
+ export { LinkSourceTag } from "./linkSourceTag"
95
+ // * RelativeDate — Recent rail — injects `/static/relativeDate.js`
96
+ // to recompute the home-page Recent rail's relative timestamps
97
+ // ("3d ago") live on every page load, instead of baking a decaying
98
+ // string at build time. Pairs with the `<span class="brain-rel-date"
99
+ // data-date="<ISO>">` markup emitted by `build_homepage`.
100
+ export { RelativeDate } from "./relativeDate"
101
+ export { CodeCopy } from "./codeCopy"
102
+ // * EmptyDoorFilter — P4.1 — strips home-page list-items whose
103
+ // internal link resolves to an empty folder (e.g. `daily/` when
104
+ // no daily notes exist). Pairs with the brain CLI's auto-generated
105
+ // `<folder>/index.md` so the door appears iff the folder has
106
+ // content.
107
+ export { EmptyDoorFilter } from "./emptyDoorFilter"
108
+ // * EmailThreadReader — P4.4 — injects the email-thread reading-mode
109
+ // runtime (`/static/emailThread.js`) plus an inline
110
+ // `window.BRAIN_USER_EMAIL` global (read from
111
+ // `process.env.BRAIN_USER_EMAIL` at build time). The runtime
112
+ // enhances `email_thread` pages with per-message data attributes
113
+ // and a "Show only my replies" filter button.
114
+ export { EmailThreadReader } from "./emailThread"
@@ -0,0 +1,205 @@
1
+ // Brain wiki redesign — link-kind classifier transformer plugin (Lane B).
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/plugins/transformers/linkKindMark.ts`
5
+ // by `brain vault render --overlay`. It does NOT compile or run from
6
+ // the brain repo itself; the imports below resolve against the
7
+ // dependencies Quartz pulls into the cloned workspace via
8
+ // `npm install`, not against any package brain ships.
9
+ //
10
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
11
+ // version restructures the transformer plugin shape or the mdast
12
+ // types it uses, pull the latest transformer reference from
13
+ // https://github.com/jackyzha0/quartz/tree/v4/quartz/plugins/transformers
14
+ // and re-apply the brain tweaks flagged below — `// brain:` for
15
+ // value/structural choices on upstream-supported fields, and
16
+ // `// brain-extension:` for keys/types that don't exist in stock
17
+ // Quartz.
18
+ //
19
+ // What this transformer does, and why: the 2026 redesign (Lane B)
20
+ // fixes the "every link looks identical" complaint by giving five
21
+ // distinct visual treatments to the five link kinds the brain wiki
22
+ // emits. The styling lives in
23
+ // `quartz_overrides/quartz/styles/brain/_links.scss` and keys off the
24
+ // `data-brain-link-kind` attribute this transformer stamps onto
25
+ // every `<a>` rendered from a markdown `link` mdast node.
26
+ //
27
+ // Classification, in priority order:
28
+ //
29
+ // 1. derived — already stamped by `derivedFenceMark` with
30
+ // `data-brain-derived="true"`. We do NOT re-walk the
31
+ // fence; we just trust the upstream stamp and label
32
+ // the kind as `derived`. This keeps fence-membership
33
+ // truth-source ownership in `derivedFenceMark.ts` and
34
+ // prevents drift if the fence shape ever changes.
35
+ // 2. tag — link URL starts with `tags/` (relative) or contains
36
+ // `/tags/` (absolute / slug-prefixed). Quartz's stock
37
+ // OFM transformer renders `#tag` syntax + `[[tags/x]]`
38
+ // wiki links into mdast `link` nodes whose `url`
39
+ // already carries the slug — so a string-prefix check
40
+ // is sufficient and cheaper than a slug normaliser.
41
+ // 3. external — URL starts with `http://`, `https://`, or
42
+ // `mailto:`. When the host config sets
43
+ // `openLinksInNewTab: true`, Quartz adds
44
+ // `target="_blank" rel="noopener"` via its CrawlLinks
45
+ // pass; the brain config leaves the default off.
46
+ // Either way, the classifier reads the URL directly
47
+ // so behavior is independent of that flag and of
48
+ // plugin ordering.
49
+ // 4. ingested — URL starts with `_ingested/` (vault-internal but
50
+ // pointing to mirrored ingested content from
51
+ // `brain ingest*`). Source-tinting (krisp/slack/gmail/
52
+ // manual) is applied at runtime by `linkSourceTag.js`
53
+ // because the source identity lives in the target
54
+ // doc's frontmatter, which a transformer can't easily
55
+ // look up at build time.
56
+ // 5. wiki — fallback for everything else. Matches both Quartz's
57
+ // stock `internal` class (resolved wiki links) and
58
+ // anything that didn't fit the above buckets.
59
+ //
60
+ // brain: registration order matters. This transformer must run
61
+ // AFTER `Plugin.ObsidianFlavoredMarkdown()` (so `[[wiki-link]]` syntax
62
+ // has been converted into mdast `link` nodes) AND AFTER
63
+ // `Plugin.DerivedFenceMark()` (so `data-brain-derived="true"` is
64
+ // already stamped by the time we read it). The brain
65
+ // `quartz.config.ts` template wires it up in the correct slot — see
66
+ // the `transformers: [...]` list there.
67
+
68
+ import type { Root, RootContent, Link } from "mdast"
69
+
70
+ import { QuartzTransformerPlugin } from "../types"
71
+
72
+ // brain-extension: stable string set the SCSS in `_links.scss` keys
73
+ // off. Adding a new kind requires (a) extending this union, (b)
74
+ // extending the classifier, (c) adding a `[data-brain-link-kind="x"]`
75
+ // rule in `_links.scss`. All three live in the same Lane B PR.
76
+ type LinkKind = "derived" | "tag" | "external" | "ingested" | "wiki"
77
+
78
+ // brain-extension: the attribute the SCSS in `_links.scss` reads.
79
+ // Namespaced `data-brain-` prefix — same convention as
80
+ // `data-brain-derived` in `derivedFenceMark.ts` — so we never collide
81
+ // with a Quartz-internal `data-*` attribute.
82
+ const KIND_ATTR = "data-brain-link-kind"
83
+
84
+ // brain-extension: the upstream stamp from `derivedFenceMark.ts` that
85
+ // signals "this link lives inside a Phase D fence." Mirrored from
86
+ // that file's `stampDerived` function. If the fence stamp ever
87
+ // changes shape, update both files together.
88
+ const DERIVED_ATTR = "data-brain-derived"
89
+
90
+ // brain: external-link URL prefixes. `mailto:` is included because
91
+ // markdown autolinks (`<fixture@example.com>`) become mdast `link` nodes with
92
+ // `mailto:` URLs; the SCSS's `↗` indicator suits them. `tel:` is
93
+ // not included — current vault content has none, and adding it later
94
+ // is a one-line change.
95
+ const EXTERNAL_PREFIXES = ["http://", "https://", "mailto:"]
96
+
97
+ // brain: ingested-tier URL prefix. The vault layout puts every
98
+ // ingested doc under `_ingested/<source>/<slug>/`, and Quartz's
99
+ // CrawlLinks resolves wiki links to those targets with that exact
100
+ // prefix. Both the leading-slash form (`/_ingested/...`) and the
101
+ // relative form (`_ingested/...`) appear in real builds depending on
102
+ // link-resolution mode, so the classifier checks both.
103
+ const INGESTED_PREFIXES = ["_ingested/", "/_ingested/", "./_ingested/"]
104
+
105
+ // brain: tag-link URL patterns. Quartz emits tag links with URL
106
+ // `tags/<slug>` (relative, default `markdownLinkResolution: "shortest"`)
107
+ // or `/tags/<slug>` (absolute) depending on render context. The check
108
+ // matches both forms; a `tags/` prefix on a non-tag doc is unlikely
109
+ // (the slug `tags` is reserved by Quartz's TagPage emitter) so we
110
+ // don't over-constrain.
111
+ const TAG_PREFIXES = ["tags/", "/tags/", "./tags/"]
112
+
113
+ // brain: case-insensitive prefix match. URLs SHOULD be lowercase but
114
+ // hand-edited markdown can carry mixed-case `MailTo:` / `HTTPS://`,
115
+ // so we normalise the comparison to be defensive.
116
+ function startsWithAny(url: string, prefixes: readonly string[]): boolean {
117
+ const lowered = url.toLowerCase()
118
+ for (const prefix of prefixes) {
119
+ if (lowered.startsWith(prefix.toLowerCase())) return true
120
+ }
121
+ return false
122
+ }
123
+
124
+ // brain: classify a single mdast Link node into one of the five
125
+ // kinds. Returns a string from the `LinkKind` union. The classifier
126
+ // is pure (no mutation, no I/O) so it can be unit-tested in isolation
127
+ // if we ever add an overlay-side TS test runner.
128
+ //
129
+ // brain: classification order matters — `derived` wins over every
130
+ // other kind because a link inside a Phase D fence is conceptually
131
+ // "an evidence edge to a related doc," not "a wiki link to that
132
+ // doc." The italic-dashed styling in `_links.scss` reflects that
133
+ // distinction. After `derived`, `tag` and `external` are mutually
134
+ // exclusive with the others by URL shape; `ingested` is a refinement
135
+ // of `wiki` (both are vault-internal) so the ingested check runs
136
+ // before the wiki fallback.
137
+ function classifyLink(link: Link): LinkKind {
138
+ const data = link.data as Record<string, unknown> | undefined
139
+ const props = data?.hProperties as Record<string, unknown> | undefined
140
+ if (props !== undefined && props[DERIVED_ATTR] === "true") {
141
+ return "derived"
142
+ }
143
+ const url = link.url ?? ""
144
+ if (startsWithAny(url, TAG_PREFIXES)) return "tag"
145
+ if (startsWithAny(url, EXTERNAL_PREFIXES)) return "external"
146
+ if (startsWithAny(url, INGESTED_PREFIXES)) return "ingested"
147
+ return "wiki"
148
+ }
149
+
150
+ // brain: in-place attribute stamper. Same `data.hProperties` extension
151
+ // point used by `derivedFenceMark.stampDerived` — remark-rehype copies
152
+ // these onto the corresponding hast element's properties, which then
153
+ // become real HTML attributes after stringification. Idempotent: if
154
+ // the attribute is already set (e.g. a future plugin pre-stamps it),
155
+ // we don't overwrite — first writer wins, and the attribute's
156
+ // presence is what the SCSS keys off, not its origin.
157
+ function stampKind(link: Link, kind: LinkKind): void {
158
+ const data = (link.data ??= {}) as Record<string, unknown>
159
+ const props = (data.hProperties ??= {}) as Record<string, string>
160
+ if (props[KIND_ATTR] !== undefined) return
161
+ props[KIND_ATTR] = kind
162
+ }
163
+
164
+ // brain: top-level mdast walker. Recursively visits every node;
165
+ // stamps any `link` it encounters. Mirrors the `"children" in node`
166
+ // recursion pattern in `derivedFenceMark.annotateFence` so links
167
+ // nested inside lists, headings, blockquotes, and table cells are
168
+ // all reached. No fence-membership tracking is needed here because
169
+ // `derivedFenceMark` runs first and has already stamped
170
+ // `data-brain-derived` on every fence link by the time we walk.
171
+ function annotate(tree: Root): void {
172
+ const visit = (node: RootContent | Root): void => {
173
+ if (node.type === "link") {
174
+ stampKind(node, classifyLink(node))
175
+ }
176
+ if ("children" in node && Array.isArray(node.children)) {
177
+ for (const child of node.children as RootContent[]) {
178
+ visit(child)
179
+ }
180
+ }
181
+ }
182
+ visit(tree)
183
+ }
184
+
185
+ // brain-extension: the transformer plugin itself. Empty options
186
+ // object — the only knobs would be the kind set or the URL prefix
187
+ // lists, both of which are part of the redesign contract documented
188
+ // at the top of this file. Keeping the signature `(opts?: never)`
189
+ // documents that no configuration is expected; if a knob is genuinely
190
+ // needed later (e.g. a per-vault tag prefix override), widen the type
191
+ // at that point rather than guessing now.
192
+ export const LinkKindMark: QuartzTransformerPlugin = (_opts?: never) => {
193
+ return {
194
+ name: "LinkKindMark",
195
+ markdownPlugins() {
196
+ return [
197
+ () => {
198
+ return (tree: Root) => {
199
+ annotate(tree)
200
+ }
201
+ },
202
+ ]
203
+ },
204
+ }
205
+ }
@@ -0,0 +1,104 @@
1
+ // Brain wiki redesign — link source-tagger transformer plugin (Lane B).
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/plugins/transformers/linkSourceTag.ts`
5
+ // by `brain vault render --overlay`. It does NOT compile or run from
6
+ // the brain repo itself; the imports below resolve against the
7
+ // dependencies Quartz pulls into the cloned workspace via
8
+ // `npm install`, not against any package brain ships.
9
+ //
10
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
11
+ // version restructures the transformer plugin shape or the
12
+ // `externalResources()` hook contract, pull the latest transformer
13
+ // reference from
14
+ // https://github.com/jackyzha0/quartz/tree/v4/quartz/plugins/transformers
15
+ // and re-apply the brain tweaks flagged below — `// brain:` for
16
+ // value/structural choices on upstream-supported fields, and
17
+ // `// brain-extension:` for keys/types that don't exist in stock
18
+ // Quartz.
19
+ //
20
+ // What this transformer does, and why: the companion `linkKindMark`
21
+ // transformer stamps `data-brain-link-kind="ingested"` on every `<a>`
22
+ // pointing into `_ingested/<source>/<slug>`, but it can't determine
23
+ // the source segment without a build-time document lookup that's
24
+ // expensive and brittle. We instead inject a tiny client script
25
+ // (`/static/linkSourceTag.js`, also installed by the overlay) that
26
+ // extracts the source from the href on `DOMContentLoaded` and stamps
27
+ // `data-brain-source="krisp"` (or slack/gmail/manual). `_links.scss`
28
+ // reads that attribute via `&[data-brain-source="krisp"]` rules to
29
+ // pick the source-tinted left-rail color from the `--brain-source-*`
30
+ // token palette.
31
+ //
32
+ // brain: this transformer ALWAYS injects the script — unlike
33
+ // `Plugin.ReloadSignal` which is gated on `BRAIN_WIKI_RELOAD=1`,
34
+ // source tagging is part of the production redesign and must run
35
+ // whether the build is dev (`bin/brain-up`) or prod (`brain vault
36
+ // render`). The injected script is tiny (~25 lines, ~1.2 KB) and
37
+ // runs once per page load + once per SPA navigation.
38
+ //
39
+ // brain: cross-references for the source-tagging contract:
40
+ // * `quartz_overrides/quartz/static/linkSourceTag.js` — the runtime
41
+ // tagger this transformer points page `<head>` at.
42
+ // * `quartz_overrides/quartz/plugins/transformers/linkKindMark.ts`
43
+ // — the build-time classifier whose `data-brain-link-kind="ingested"`
44
+ // stamp this script complements with `data-brain-source="..."`.
45
+ // * `quartz_overrides/quartz/styles/brain/_links.scss` — the SCSS
46
+ // consumer that reads both attributes to render the source-tinted
47
+ // left rail on ingested links.
48
+ // * `quartz_overrides/quartz/styles/brain/_tokens.scss` — defines
49
+ // the `--brain-source-{krisp,slack,gmail,manual}` palette both the
50
+ // graph and link styles share.
51
+ //
52
+ // Registration: this transformer has NO ordering requirement among
53
+ // the other transformers — it doesn't touch the mdast tree, only
54
+ // contributes a `<script>` tag via `externalResources()`. Place it
55
+ // anywhere in the `transformers: [...]` list; the brain
56
+ // `quartz.config.ts` template wires it next to `LinkKindMark` so the
57
+ // Lane B plugins stay grouped.
58
+
59
+ import { QuartzTransformerPlugin } from "../types"
60
+
61
+ // brain-extension: the path the page `<script src=...>` tag points
62
+ // at. Leading slash is intentional — Quartz emits pages at arbitrary
63
+ // slug depths (e.g. `/notes/foo/index.html`) and a relative
64
+ // `static/linkSourceTag.js` would break for any non-root page.
65
+ // Server-rooted `/static/linkSourceTag.js` resolves consistently
66
+ // regardless of which page the user landed on. This works because
67
+ // Caddy's `file_server` (and Quartz's stock `file_server` in dev)
68
+ // serves the build dir as the site root
69
+ // (`<vault>/.quartz/current/static/linkSourceTag.js`).
70
+ const SCRIPT_SRC = "/static/linkSourceTag.js"
71
+
72
+ // brain-extension: the transformer plugin itself. Empty options
73
+ // shape — the script path is part of the contract documented above
74
+ // and the script body is fixed at the static-asset level. Keeping
75
+ // the signature `(opts?: never)` documents that no configuration is
76
+ // expected; if a knob is genuinely needed later (e.g. a per-vault
77
+ // source-allowlist override), widen the type at that point rather
78
+ // than guessing now.
79
+ export const LinkSourceTag: QuartzTransformerPlugin = (_opts?: never) => {
80
+ return {
81
+ name: "LinkSourceTag",
82
+ externalResources() {
83
+ return {
84
+ js: [
85
+ {
86
+ // brain: `afterDOMReady` so the script runs after
87
+ // `</body>` (see Quartz's `renderPage.tsx` for the
88
+ // dispatch). The tagger reads + writes DOM attributes
89
+ // and uses its own `DOMContentLoaded` listener as a
90
+ // fallback for the loading-state edge case.
91
+ loadTime: "afterDOMReady",
92
+ // brain-extension: external script (URL src) rather than
93
+ // inline. The linkSourceTag.js file is bundled into every
94
+ // build's `static/` dir by Quartz's stock `Plugin.Static()`
95
+ // emitter; pointing at it by URL keeps the script body
96
+ // out of every page's HTML and lets the browser cache it.
97
+ contentType: "external",
98
+ src: SCRIPT_SRC,
99
+ },
100
+ ],
101
+ }
102
+ },
103
+ }
104
+ }
@@ -0,0 +1,100 @@
1
+ // Brain wiki — live relative-date transformer plugin (Recent rail).
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/plugins/transformers/relativeDate.ts`
5
+ // by `brain vault render --overlay`. It does NOT compile or run from
6
+ // the brain repo itself; the imports below resolve against the
7
+ // dependencies Quartz pulls into the cloned workspace via
8
+ // `npm install`, not against any package brain ships.
9
+ //
10
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
11
+ // version restructures the transformer plugin shape or the
12
+ // `externalResources()` hook contract, pull the latest transformer
13
+ // reference from
14
+ // https://github.com/jackyzha0/quartz/tree/v4/quartz/plugins/transformers
15
+ // and re-apply the brain tweaks flagged below — `// brain:` for
16
+ // value/structural choices on upstream-supported fields, and
17
+ // `// brain-extension:` for keys/types that don't exist in stock
18
+ // Quartz.
19
+ //
20
+ // What this transformer does, and why: the home-page Recent rail
21
+ // (`brain.wiki.build_homepage`) used to bake a relative date string
22
+ // ("today", "3d ago") into `index.md` at build time. That string decays
23
+ // because the home note isn't re-rendered daily, so a doc ingested 3
24
+ // days before a build still reads "3d ago" weeks later. The rail now
25
+ // emits the ABSOLUTE date as a `data-date` attribute on a
26
+ // `<span class="brain-rel-date">` plus a non-decaying "Jun 10"-style
27
+ // fallback as the span's text. We inject a tiny client script
28
+ // (`/static/relativeDate.js`, also installed by the overlay) that reads
29
+ // each `data-date` and recomputes the relative text live on every page
30
+ // load (and on Quartz SPA `nav` events), keeping the rail honest.
31
+ //
32
+ // brain: this transformer ALWAYS injects the script — unlike
33
+ // `Plugin.ReloadSignal` which is gated on `BRAIN_WIKI_RELOAD=1`, the
34
+ // live relative date is part of the production behavior and must run
35
+ // whether the build is dev (`bin/brain-up`) or prod (`brain vault
36
+ // render`). The injected script is tiny (~30 lines) and runs once per
37
+ // page load + once per SPA navigation.
38
+ //
39
+ // brain: cross-references for the live-relative-date contract:
40
+ // * `quartz_overrides/quartz/static/relativeDate.js` — the runtime
41
+ // recomputer this transformer points page `<head>` at; its bucket
42
+ // logic mirrors `build_homepage._format_relative_date`.
43
+ // * `src/brain/wiki/build_homepage.py` — the server side that emits
44
+ // `<span class="brain-rel-date" data-date="<ISO>">{absolute}</span>`
45
+ // into the Recent fence (`_render_bullets` / `_format_absolute_date`).
46
+ //
47
+ // Registration: this transformer has NO ordering requirement among
48
+ // the other transformers — it doesn't touch the mdast tree, only
49
+ // contributes a `<script>` tag via `externalResources()`. Place it
50
+ // anywhere in the `transformers: [...]` list; the brain
51
+ // `quartz.config.ts` template wires it next to `LinkSourceTag` so the
52
+ // script-only brain transformers stay grouped.
53
+
54
+ import { QuartzTransformerPlugin } from "../types"
55
+
56
+ // brain-extension: the path the page `<script src=...>` tag points at.
57
+ // Leading slash is intentional — Quartz emits pages at arbitrary slug
58
+ // depths (e.g. `/notes/foo/index.html`) and a relative
59
+ // `static/relativeDate.js` would break for any non-root page.
60
+ // Server-rooted `/static/relativeDate.js` resolves consistently
61
+ // regardless of which page the user landed on. (In practice only the
62
+ // home page carries `.brain-rel-date` spans today, but injecting site-
63
+ // wide keeps the contract simple and future-proof if other surfaces
64
+ // adopt the span.) This works because Caddy's `file_server` (and
65
+ // Quartz's stock `file_server` in dev) serves the build dir as the site
66
+ // root (`<vault>/.quartz/current/static/relativeDate.js`).
67
+ const SCRIPT_SRC = "/static/relativeDate.js"
68
+
69
+ // brain-extension: the transformer plugin itself. Empty options shape —
70
+ // the script path is part of the contract documented above and the
71
+ // script body is fixed at the static-asset level. Keeping the signature
72
+ // `(opts?: never)` documents that no configuration is expected; if a
73
+ // knob is genuinely needed later, widen the type at that point rather
74
+ // than guessing now.
75
+ export const RelativeDate: QuartzTransformerPlugin = (_opts?: never) => {
76
+ return {
77
+ name: "RelativeDate",
78
+ externalResources() {
79
+ return {
80
+ js: [
81
+ {
82
+ // brain: `afterDOMReady` so the script runs after `</body>`
83
+ // (see Quartz's `renderPage.tsx` for the dispatch). The
84
+ // recomputer reads + writes DOM text and uses its own
85
+ // `DOMContentLoaded` listener as a fallback for the
86
+ // loading-state edge case.
87
+ loadTime: "afterDOMReady",
88
+ // brain-extension: external script (URL src) rather than
89
+ // inline. The relativeDate.js file is bundled into every
90
+ // build's `static/` dir by Quartz's stock `Plugin.Static()`
91
+ // emitter; pointing at it by URL keeps the script body out
92
+ // of every page's HTML and lets the browser cache it.
93
+ contentType: "external",
94
+ src: SCRIPT_SRC,
95
+ },
96
+ ],
97
+ }
98
+ },
99
+ }
100
+ }
@@ -0,0 +1,131 @@
1
+ // Brain blue-green serve — reload-signal transformer plugin.
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/plugins/transformers/reloadSignal.ts`
5
+ // by `brain vault render --overlay`. It does NOT compile or run
6
+ // from the brain repo itself; the imports below resolve against
7
+ // the dependencies Quartz pulls into the cloned workspace via
8
+ // `npm install`, not against any package brain ships.
9
+ //
10
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
11
+ // version restructures the transformer plugin shape or the
12
+ // `externalResources()` hook contract, pull the latest
13
+ // transformer reference from
14
+ // https://github.com/jackyzha0/quartz/tree/v4/quartz/plugins/transformers
15
+ // and re-apply the brain tweaks flagged below — `// brain:` for
16
+ // value/structural choices on upstream-supported fields, and
17
+ // `// brain-extension:` for keys/types that don't exist in stock
18
+ // Quartz.
19
+ //
20
+ // What this transformer does, and why: in the blue-green serve
21
+ // architecture (see
22
+ // `docs/plans/2026-05-01-quartz-blue-green-serve.md`), Caddy
23
+ // serves whichever build dir `<vault>/.quartz/current` symlinks
24
+ // to, and the brain build watcher atomically retargets that
25
+ // symlink at a fresh `<vault>/.quartz/builds/<id>/` after every
26
+ // rebuild. Stock Quartz's `--serve` mode opens a WebSocket from
27
+ // the dev server back to the page so it can push a reload — but
28
+ // `--serve` is dead in our flow (Caddy serves the static output
29
+ // directly; Quartz only builds), so the WebSocket-reload path is
30
+ // gone. This transformer injects an ETag-aware polling client
31
+ // (`quartz/static/reload.js`, also installed by the overlay) that
32
+ // fetches `/.build-id` every 3s, revalidates with `If-None-Match`
33
+ // after the first ETag-bearing response, and calls
34
+ // `location.reload()` when the value changes. The polling target is
35
+ // the per-build identifier file `brain.wiki.build_swap` writes at
36
+ // each build dir's root; Caddy serves it directly because the build
37
+ // dir IS the site root.
38
+ //
39
+ // brain-extension: env-var contract. The script is injected only
40
+ // when `process.env.BRAIN_WIKI_RELOAD === "1"` at BUILD time
41
+ // (not at runtime — the gate is read while Quartz is producing
42
+ // HTML, then baked into the page). Any other value (including
43
+ // unset) means `externalResources()` returns an empty object and
44
+ // no `<script>` tag goes out. `bin/brain-up` (the dev daily-use
45
+ // path) sets the env to `"1"`; `brain vault render` (the prod
46
+ // one-shot path) leaves it unset, so prod builds ship without
47
+ // any polling chatter. The reload.js asset itself is still
48
+ // copied into `static/` by Quartz's `Plugin.Static()` emitter
49
+ // either way — it's just unreferenced unless the gate flips.
50
+ //
51
+ // brain: cross-references for the full reload contract:
52
+ // * `quartz_overrides/quartz/static/reload.js` — the polling
53
+ // client this transformer points page `<head>` at.
54
+ // * `src/brain/wiki/build_swap.py` — writes `<build>/.build-id`
55
+ // after every successful build, then atomically retargets
56
+ // `<vault>/.quartz/current` at the new build dir.
57
+ // * `bin/brain-up` — sets `BRAIN_WIKI_RELOAD=1` for the build
58
+ // watcher subprocess; spawns Caddy as the static file server
59
+ // pointed at `<vault>/.quartz/current/`.
60
+ // * Caddyfile — must serve `/.build-id` at root (default
61
+ // behavior of `file_server` since the file lives at the
62
+ // build dir's root).
63
+ //
64
+ // Registration: this transformer has NO ordering requirement
65
+ // among the other transformers — it doesn't touch the mdast
66
+ // tree. It only contributes a `<script>` tag via
67
+ // `externalResources()`. Place it anywhere in the
68
+ // `transformers: [...]` list; the brain `quartz.config.ts`
69
+ // template wires it AFTER `Plugin.DerivedFenceMark()` so the two
70
+ // brain-extension transformers stay grouped.
71
+
72
+ import { QuartzTransformerPlugin } from "../types"
73
+
74
+ // brain-extension: the path the page `<script src=...>` tag
75
+ // points at. Leading slash is intentional — Quartz emits pages
76
+ // at arbitrary slug depths (e.g. `/notes/foo/index.html`) and a
77
+ // relative `static/reload.js` would break for any non-root page.
78
+ // Server-rooted `/static/reload.js` resolves consistently
79
+ // regardless of which page the user landed on. This works
80
+ // because Caddy's `file_server` serves the build dir as the site
81
+ // root (`<vault>/.quartz/current/static/reload.js`); see
82
+ // `docs/plans/2026-05-01-quartz-blue-green-serve.md` for the
83
+ // Caddyfile shape.
84
+ const RELOAD_SCRIPT_SRC = "/static/reload.js"
85
+
86
+ // brain-extension: build-time env var that gates injection. Read
87
+ // inside `externalResources()` so each plugin instance evaluates
88
+ // the gate fresh per build (matters for tests + for `bin/brain-up`
89
+ // re-invocations where the env may differ between runs).
90
+ const RELOAD_ENV_VAR = "BRAIN_WIKI_RELOAD"
91
+
92
+ // brain-extension: the transformer plugin itself. Empty options
93
+ // shape — the only knobs would be the script path and the env
94
+ // var name, both of which are part of the contract documented
95
+ // above. Keeping the signature `(opts?: never)` documents that
96
+ // no configuration is expected; if a knob is genuinely needed
97
+ // later (e.g. a per-vault interval override), widen the type at
98
+ // that point rather than guessing now.
99
+ export const ReloadSignal: QuartzTransformerPlugin = (_opts?: never) => {
100
+ return {
101
+ name: "ReloadSignal",
102
+ externalResources() {
103
+ // brain: strict equality with `"1"` rather than truthiness
104
+ // so accidental values like `"0"` / `"false"` / `"no"`
105
+ // don't enable the script. Symmetric with the contract
106
+ // documented in `bin/brain-up` (which sets exactly `"1"`).
107
+ if (process.env[RELOAD_ENV_VAR] !== "1") {
108
+ return {}
109
+ }
110
+ return {
111
+ js: [
112
+ {
113
+ // brain: `afterDOMReady` so the script runs after
114
+ // `</body>` (see Quartz's `renderPage.tsx` for the
115
+ // dispatch). The poller doesn't touch the DOM at
116
+ // load time, and afterDOMReady avoids blocking
117
+ // initial paint on the network round-trip.
118
+ loadTime: "afterDOMReady",
119
+ // brain-extension: external script (URL src) rather
120
+ // than inline. The reload.js file is bundled into
121
+ // every build's `static/` dir by Quartz's stock
122
+ // `Plugin.Static()` emitter; pointing at it by URL
123
+ // keeps the script body out of every page's HTML.
124
+ contentType: "external",
125
+ src: RELOAD_SCRIPT_SRC,
126
+ },
127
+ ],
128
+ }
129
+ },
130
+ }
131
+ }