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,168 @@
1
+ // Brain blue-green serve — client-side reload watcher.
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/static/reload.js` by `brain vault render
5
+ // --overlay`, and is copied verbatim into every build's `static/`
6
+ // dir by Quartz's stock `Plugin.Static()` emitter (no extra wiring
7
+ // needed — Quartz already mirrors `quartz/static/` 1:1 into the
8
+ // build output). The script is referenced from page `<head>` only
9
+ // when the brain `Plugin.ReloadSignal` transformer's
10
+ // `externalResources()` hook decides to inject it.
11
+ //
12
+ // Tested against Quartz v4.5.x (April 2026). The file is plain
13
+ // vanilla JS (no transpile, no bundler) so it survives any future
14
+ // Quartz refactor of its plugin pipeline — the only contract is
15
+ // that `Plugin.Static()` keeps copying `quartz/static/*` into
16
+ // `<build>/static/`. If a future Quartz version changes the static
17
+ // asset pipeline, update both this file's load path and the
18
+ // `reloadSignal.ts` transformer in lock-step.
19
+ //
20
+ // What this script 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. The brain build watcher rebuilds the site into a fresh
25
+ // `<vault>/.quartz/builds/<id>/` dir on every vault change, then
26
+ // atomically retargets `current` at the new build. Because the
27
+ // swap happens server-side (rename(2) on a symlink), open
28
+ // browsers have no signal that the page they're viewing is now
29
+ // stale. Quartz's own `--serve` mode opens a WebSocket from the
30
+ // dev server to reload tabs in place, but `--serve` is dead in
31
+ // our flow (we serve via Caddy, not Quartz) — so this polling
32
+ // loop is the replacement reload mechanism.
33
+ //
34
+ // Mechanism: every 1s, fetch `/.build-id` (a one-line text file
35
+ // `brain.wiki.build_swap` writes to each build dir) as a conditional
36
+ // request after the first successful response. Caddy serves the file
37
+ // with an ETag; this client reuses that ETag via `If-None-Match` so
38
+ // unchanged builds return `304 Not Modified` without a response body.
39
+ // The first 200 response is captured as the baseline; any subsequent
40
+ // 200 response with a different value triggers `location.reload()`.
41
+ // Network errors, non-2xx responses, and 304s are swallowed silently —
42
+ // a transient blip must NOT reset the baseline (that would cause
43
+ // spurious reloads on the next successful poll). Polling pauses while
44
+ // the tab is hidden (`document.visibilityState === "hidden"`) and
45
+ // resumes on `visibilitychange` to "visible" so backgrounded tabs
46
+ // don't burn battery / network.
47
+ //
48
+ // Env-var contract: the script tag itself is gated by
49
+ // `BRAIN_WIKI_RELOAD=1` at Quartz BUILD time (not at script
50
+ // runtime). When the env var is unset or any other value, the
51
+ // `Plugin.ReloadSignal` transformer returns `{}` from its
52
+ // `externalResources()` hook and no script tag is injected —
53
+ // the script file is still copied into `static/` by Quartz, but
54
+ // nothing references it. `bin/brain-up` (the dev daily-use
55
+ // path) sets the env to `"1"`; `brain vault render` (the prod
56
+ // one-shot path) leaves it unset, so prod builds ship without
57
+ // any polling chatter.
58
+ //
59
+ // Browser support: targets evergreen Chrome / Firefox / Safari.
60
+ // No polyfills, no IE, no transpile. Uses `fetch`, async/await,
61
+ // and `document.visibilityState` — all baseline since 2017.
62
+ ;(function () {
63
+ // Polling cadence in ms. 1s keeps edit-to-UI latency at ≤1s for
64
+ // warm builds. Server load remains trivial: responses are
65
+ // ETag-gated (304 No Body on unchanged builds), so
66
+ // 10 tabs × 1s = ~10 req/s against Caddy, still negligible for
67
+ // a UUID-sized static file.
68
+ var INTERVAL_MS = 1000
69
+
70
+ // Path to the per-build identifier. `brain.wiki.build_swap`
71
+ // writes a one-line file at the build dir's root; Caddy's
72
+ // file_server serves it directly because the build dir IS the
73
+ // site root. Trailing newline is normal — strip it before
74
+ // comparing.
75
+ var BUILD_ID_PATH = "/.build-id"
76
+
77
+ // Must match full-build ids from `brain.wiki.build_swap._generate_build_id()`
78
+ // and fast-path ids from `quartz/cli/build_partial_handler.js`. Rejecting
79
+ // non-empty garbage bodies (for example an HTML fallback) prevents a
80
+ // bad 200 response from poisoning the ETag baseline.
81
+ var BUILD_ID_PATTERN = /^(?:\d{8}-\d{6}-[0-9a-f]{6}|fastpath-\d+-[0-9a-f]{8})$/
82
+
83
+ // Last ETag returned by Caddy for `/.build-id`. `null` means the
84
+ // first request has not completed yet, so no conditional header can
85
+ // be sent. This is independent from the baseline body value: the
86
+ // ETag saves bandwidth on unchanged builds, while the body comparison
87
+ // remains the correctness check that decides when to reload.
88
+ var lastEtag = null
89
+
90
+ // Baseline build id captured on the first successful poll.
91
+ // `null` means "not yet known"; any response other than the
92
+ // baseline triggers a reload. Once set, this is NEVER cleared
93
+ // by a network failure — that would cause the next successful
94
+ // poll to silently rebaseline against a possibly-newer build
95
+ // and miss the swap.
96
+ var baseline = null
97
+
98
+ // Single shared interval handle so we can `clearInterval` on
99
+ // visibilitychange. `null` means "not currently polling".
100
+ var timer = null
101
+
102
+ function buildHeaders() {
103
+ if (lastEtag === null) return {}
104
+ return { "If-None-Match": lastEtag }
105
+ }
106
+
107
+ async function poll() {
108
+ try {
109
+ var r = await fetch(BUILD_ID_PATH, {
110
+ cache: "no-cache",
111
+ headers: buildHeaders(),
112
+ })
113
+ if (r.status === 304) return
114
+ if (!r.ok) return
115
+ var etag = r.headers.get("ETag")
116
+ var id = (await r.text()).trim()
117
+ if (!BUILD_ID_PATTERN.test(id)) return
118
+ if (etag) {
119
+ lastEtag = etag
120
+ }
121
+ if (baseline === null) {
122
+ baseline = id
123
+ // One-time banner so the user can confirm the watcher is
124
+ // wired up by opening DevTools. Subsequent polls are silent.
125
+ // eslint-disable-next-line no-console
126
+ console.log("[brain] reload watcher active (build = " + id + ")")
127
+ return
128
+ }
129
+ if (id !== baseline) {
130
+ location.reload()
131
+ }
132
+ } catch (_) {
133
+ // Network blip / DNS hiccup / Caddy bouncing. Keep polling
134
+ // — do NOT reset `baseline`, otherwise the next successful
135
+ // poll would silently rebaseline and we'd miss the next
136
+ // real swap.
137
+ }
138
+ }
139
+
140
+ function start() {
141
+ if (timer !== null) return
142
+ timer = setInterval(poll, INTERVAL_MS)
143
+ // Fire one immediately so the baseline is captured without
144
+ // waiting for the first interval tick.
145
+ poll()
146
+ }
147
+
148
+ function stop() {
149
+ if (timer === null) return
150
+ clearInterval(timer)
151
+ timer = null
152
+ }
153
+
154
+ document.addEventListener("visibilitychange", function () {
155
+ if (document.visibilityState === "hidden") {
156
+ stop()
157
+ } else {
158
+ start()
159
+ }
160
+ })
161
+
162
+ // Kick off immediately if the tab is foreground at script load.
163
+ // (afterDOMReady scripts run after `</body>`, so the document
164
+ // is parsed and we can read `visibilityState` safely.)
165
+ if (document.visibilityState !== "hidden") {
166
+ start()
167
+ }
168
+ })()
@@ -0,0 +1,252 @@
1
+ // Brain article surface — Lane C of the 2026 commonplace-book redesign.
2
+ //
3
+ // This file is installed at
4
+ // `<vault>/.quartz/quartz/styles/brain/_article.scss` by `brain vault
5
+ // render --overlay` and pulled into the global SCSS via `custom.scss`'s
6
+ // `@use "./brain/article"` line.
7
+ //
8
+ // Scope of this file:
9
+ //
10
+ // * `measure` — cap article column width at 68ch via `.popover-hint`
11
+ // so prose doesn't sprawl on a 1440px viewport. The Quartz article
12
+ // wrapper is `.popover-hint` (used both for live popover hints AND
13
+ // as the article content container — see `Component.Body`).
14
+ // * Heading rhythm — sizes pulled from the `_tokens.scss` modular
15
+ // scale (h1 `--fs-3xl`, h2 `--fs-2xl`, h3 `--fs-xl`). h2 gets a
16
+ // 1px hairline rule below in `--rule` to mark major section
17
+ // breaks editorially.
18
+ // * Blockquote — repurposed for editorial pull-quotes inside the
19
+ // article. Left rule 3px in `--accent-soft`, italic Fraunces
20
+ // (via `var(--displayFont)`), generous breathing room.
21
+ // * Article meta — `time` + read-time small-caps via `--fs-xs` +
22
+ // `+0.08em` tracking, dimmed via `--ink-2`. Quartz's
23
+ // `Component.ContentMeta` emits `<p class="content-meta">` with
24
+ // `<time>` + `<span>` children.
25
+ // * Tag chips at the article header — re-apply Lane B's `.tag-link`
26
+ // pill styling pattern. Lane B already targets `.tag-link` via
27
+ // the comma-form selector in `_links.scss`; this file owns only
28
+ // the *container* spacing on the `<ul class="tags">` wrapper
29
+ // emitted by `Component.TagList`.
30
+ //
31
+ // Scope NOT covered here:
32
+ //
33
+ // * Drop cap + pull quote — Lane D (`brain/article` extension via
34
+ // `_motion.scss` / Lane D's article additions). This file is the
35
+ // baseline rhythm; Lane D adds the editorial flourishes on top.
36
+ // * Code surface — `_code.scss`.
37
+ // * Callout chrome — `_callouts.scss`.
38
+ //
39
+ // Tokens consumed (all from `_tokens.scss`):
40
+ //
41
+ // * `--fs-3xl` / `--fs-2xl` / `--fs-xl` — heading scale
42
+ // * `--fs-xs` / `--fs-sm` — meta + tag-chip type
43
+ // * `--rule` — h2 hairline + tag chip border
44
+ // * `--accent-soft` — blockquote left rule
45
+ // * `--ink-1` / `--ink-2` — ink scale for meta + body
46
+ // * `--displayFont` — Fraunces for italic blockquote
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // 1. Article measure
50
+ // ---------------------------------------------------------------------------
51
+ //
52
+ // `.popover-hint` is the wrapper Quartz emits around article bodies
53
+ // AND popover hints (`Component.Body` + `popover.inline.ts`). Capping
54
+ // it at 68ch gives long-form prose a comfortable line length without
55
+ // touching the grid columns from base.scss.
56
+ //
57
+ // brain: 68ch is a deliberate trade-off. The classical body-text
58
+ // measure is 45–75 characters; 68ch lands at the comfortable upper
59
+ // end so technical prose with code identifiers doesn't wrap awkwardly,
60
+ // and on a 1440px viewport the article still fills the center column
61
+ // rather than collapsing to a narrow strip. Newsreader at `--fs-base`
62
+ // makes 68ch ≈ 660px.
63
+ // brain (Linear variant): bumped from 68ch to 95ch — Geist is narrower
64
+ // per character than Newsreader, so the same line-count needs a wider
65
+ // measure to read at body size. 95ch on a 1440px viewport with 320px
66
+ // sidebars leaves the article filling the center column with no dead
67
+ // space; on wider viewports the cap still holds against runaway lines.
68
+ .popover-hint {
69
+ max-width: 95ch;
70
+ }
71
+
72
+ // brain (Linear-variant feedback): horizontal breathing room for the
73
+ // article column. Stock Quartz pins `.center` flush against the two
74
+ // sidebar columns with only the grid gap (5px) as separation, which
75
+ // reads as cramped on the redesigned layout. 2rem of horizontal
76
+ // padding gives the prose room to breathe between the explorer/search
77
+ // rail on the left and the graph/ToC/backlinks rail on the right.
78
+ //
79
+ // box-sizing + min-width are CRITICAL: stock Quartz's body grid is
80
+ // `grid-template-columns: 320px auto 320px`. The `auto` track sizes
81
+ // to its content's intrinsic width, so naive `padding: 0 2rem` on
82
+ // `.center` (with default content-box) made the track grow 64px
83
+ // wider than the available space, pushing the article over the right
84
+ // sidebar and clipping its right-edge text. `box-sizing: border-box`
85
+ // pulls padding INTO the element's declared width; `min-width: 0`
86
+ // tells the grid algorithm not to inflate the track to fit the
87
+ // content's min-content size. Together they keep the center track
88
+ // at its natural ~790px width on a 1440 viewport, with the inner
89
+ // content area at 790 - 64 = 726px (well above readable measure).
90
+ .center {
91
+ box-sizing: border-box;
92
+ min-width: 0;
93
+ padding-left: 2rem;
94
+ padding-right: 2rem;
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // 2. Heading hierarchy + rhythm
99
+ // ---------------------------------------------------------------------------
100
+ //
101
+ // Sizes from `_tokens.scss`. Stock Quartz's `base.scss` paints
102
+ // `h1: 1.75rem`, `h2: 1.4rem`, `h3: 1.12rem` — flat and unrelated to
103
+ // our modular scale. The `_typography.scss` Lane A file already pins
104
+ // h1–h6 to the scale; this block adds the *editorial* treatment
105
+ // (rule below h2, tighter top margins inside articles, anchor-link
106
+ // glyph color).
107
+ //
108
+ // Selectors are scoped under `article` so they only fire inside the
109
+ // article column — popover hints and breadcrumbs don't grow horns.
110
+
111
+ article {
112
+ > h1 {
113
+ font-size: var(--fs-3xl);
114
+ margin-top: 0;
115
+ margin-bottom: 1rem;
116
+ }
117
+
118
+ > h2 {
119
+ font-size: var(--fs-2xl);
120
+ margin-top: 2rem;
121
+ margin-bottom: 0.8rem;
122
+ // brain: hairline rule below every h2 marks major section breaks
123
+ // editorially — it reads like a page-layout rule in a printed book
124
+ // rather than a hard divider. The rule sits 0.4rem below the
125
+ // baseline so descenders don't graze it.
126
+ padding-bottom: 0.4rem;
127
+ border-bottom: 1px solid var(--rule);
128
+ }
129
+
130
+ > h3 {
131
+ font-size: var(--fs-xl);
132
+ margin-top: 1.6rem;
133
+ margin-bottom: 0.6rem;
134
+ }
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // 3. Blockquote — editorial pull-quote treatment
139
+ // ---------------------------------------------------------------------------
140
+ //
141
+ // Stock Quartz paints `blockquote { border-left: 3px solid var(--secondary) }`
142
+ // (see `base.scss` line 349-354). We swap the rule color to
143
+ // `--accent-soft` (12% terracotta) so the rule reads as a soft visual
144
+ // accent rather than a hard partition, route the body through Fraunces
145
+ // italic for a pull-quote voice, and add generous vertical breathing
146
+ // room.
147
+ //
148
+ // brain (Lane D audit fix 2026-05-02): the original selector was
149
+ // `article blockquote:not(.callout blockquote)` — a descendant
150
+ // `:not()` argument that excludes blockquotes nested INSIDE callout
151
+ // blockquotes (a structure Quartz never emits). The actual Quartz
152
+ // markup for callouts is `<blockquote class="callout note">` (see
153
+ // `quartz/plugins/transformers/ofm.ts` line 499 — the blockquote IS
154
+ // the callout). The wrong selector applied this rule to every
155
+ // callout, overriding the per-type color rule with `--accent-soft`.
156
+ // Correct exclusion is `:not(.callout)` so callout chrome stays
157
+ // owned by `_callouts.scss`. The same selector bug is fixed below
158
+ // in the Lane D pull-quote rule.
159
+
160
+ article blockquote:not(.callout) {
161
+ border-left-color: var(--accent-soft);
162
+ margin: 1.6rem 0;
163
+ padding: 0.4rem 0 0.4rem 1.2rem;
164
+ font-size: var(--fs-md);
165
+ color: var(--ink-1);
166
+ line-height: 1.4;
167
+
168
+ // Trim default paragraph margins inside the quote so a single-`<p>`
169
+ // quote doesn't read as a stack of margin gaps.
170
+ > p {
171
+ margin: 0.3rem 0;
172
+ }
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // 4. Article meta — time + read-time
177
+ // ---------------------------------------------------------------------------
178
+ //
179
+ // Quartz emits `<p class="content-meta"><time>…</time><span>…</span></p>`
180
+ // from `Component.ContentMeta`. Treat the row as a metadata caption:
181
+ // small-caps tracking, dimmed ink, tight leading. The `show-comma`
182
+ // attribute Quartz adds drives a `::after` "," separator we keep.
183
+
184
+ .content-meta {
185
+ // brain (Linear): mono metadata is a Linear hallmark — Geist Mono
186
+ // at small size with a touch of tracking reads as tech-forward
187
+ // chrome rather than editorial small-caps.
188
+ font-family: var(--codeFont);
189
+ font-size: 0.75rem;
190
+ color: var(--ink-2);
191
+ letter-spacing: 0.02em;
192
+ margin: 0.2rem 0 0.8rem 0;
193
+ line-height: 1.3;
194
+
195
+ // Lining tabular numerals inside metadata so dates and read-time
196
+ // counts align cleanly.
197
+ font-feature-settings: "lnum", "tnum";
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // 5. TagList container at the article header
202
+ // ---------------------------------------------------------------------------
203
+ //
204
+ // `Component.TagList` emits `<ul class="tags"><li><a class="internal tag-link">…</a></li></ul>`.
205
+ // Lane B's `_links.scss` already styles `.tag-link` as a pill chip
206
+ // via the comma-form selector. Here we own only the wrapper layout:
207
+ // inline-flex row with consistent gap, no list bullets, top margin
208
+ // matching the meta row.
209
+
210
+ ul.tags {
211
+ list-style: none;
212
+ padding: 0;
213
+ margin: 0.4rem 0 1rem 0;
214
+ display: flex;
215
+ flex-wrap: wrap;
216
+ gap: 0.4rem;
217
+
218
+ > li {
219
+ margin: 0;
220
+ padding: 0;
221
+ }
222
+ }
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // 6. Linear pull-quote treatment for `<blockquote>`
226
+ // ---------------------------------------------------------------------------
227
+ //
228
+ // The commonplace-book lane appended a Fraunces-italic drop cap and
229
+ // a serif pull-quote here. The Linear variant drops both:
230
+ //
231
+ // * No drop cap. Tech aesthetic doesn't open articles with a
232
+ // literary capital — articles begin flush.
233
+ // * Pull quote retuned: Geist semibold at `--fs-lg`, indigo-violet
234
+ // left rule at full strength (not soft alpha), no italic. Reads
235
+ // as a tech-forward "callout" moment in the article rather than
236
+ // an editorial flourish.
237
+ //
238
+ // Same `:not(.callout)` exclusion so callout chrome stays owned by
239
+ // `_callouts.scss`. This rule comes AFTER the Lane C blockquote rule
240
+ // above, so cascade resolves these properties over Lane C's; the
241
+ // Lane C `border-left-color: var(--accent-soft)` is overridden to
242
+ // `var(--accent-strong)` here.
243
+
244
+ article blockquote:not(.callout) {
245
+ font-size: var(--fs-lg);
246
+ line-height: 1.5;
247
+ font-weight: 500;
248
+ padding-left: 1.25rem;
249
+ border-left-color: var(--accent-strong);
250
+ margin: 2rem 0;
251
+ color: var(--ink-1);
252
+ }
@@ -0,0 +1,113 @@
1
+ // Brain atmosphere — Linear-style variant of the 2026 redesign.
2
+ //
3
+ // This file is installed at
4
+ // `<vault>/.quartz/quartz/styles/brain/_atmosphere.scss` by `brain
5
+ // vault render --overlay` and pulled into the global SCSS via
6
+ // `custom.scss`'s `@use "./brain/atmosphere"` line (between
7
+ // `surface` and `motion`).
8
+ //
9
+ // What this file owns in the Linear variant — clean tech-forward
10
+ // chrome, NOT print-shop atmosphere:
11
+ //
12
+ // 1. Body stacking-context lock — defensive isolation so any
13
+ // future negative-z body pseudo-elements don't sink behind
14
+ // `<html>` styling. Inherited from the commonplace-book lane;
15
+ // kept because it's harmless and architecturally sound.
16
+ // 2. Hairline column rule between the left sidebar and the article
17
+ // column — drawn via an absolute-positioned `::after` on
18
+ // `.sidebar.left`. Same treatment as the commonplace-book lane;
19
+ // reads as clean tech chrome on neutral surfaces.
20
+ // 3. Hairline section break — `<hr>` inside articles renders as a
21
+ // flat 1px rule in `--rule`. Replaces the commonplace-book
22
+ // fleuron with a purely-utilitarian divider.
23
+ //
24
+ // What was REMOVED from the commonplace-book lane:
25
+ // * Paper grain `body::before` SVG noise texture — Linear surfaces
26
+ // are clean, never textured.
27
+ // * Fleuron (❦) section ornament — not appropriate for the tech
28
+ // aesthetic.
29
+ //
30
+ // What does NOT live here:
31
+ // * Page-load reveal animations + reduced-motion gate — `_motion.scss`.
32
+ // * Drop cap / pull quote — `_article.scss`.
33
+ // * Marginalia (backlinks-as-side-notes) — `_marginalia.scss`.
34
+ //
35
+ // Tokens consumed (all from `_tokens.scss`):
36
+ // * `--rule` — column rule + hr hairline
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // 1. Body stacking-context lock
40
+ // ---------------------------------------------------------------------------
41
+ //
42
+ // brain (Linear-variant feedback): the `body { isolation: isolate }` rule
43
+ // from the Lane D audit is removed in this variant. It was added to lock
44
+ // a stacking context for the paper-grain `body::before` pseudo-element so
45
+ // `z-index: -1` would paint reliably; the Linear variant has no paper
46
+ // grain (the `body::before` rule is gone earlier in this file), so the
47
+ // isolation rule has no consumer.
48
+ //
49
+ // Per spec, `isolation: isolate` on `body` should NOT create a containing
50
+ // block for `position: fixed` descendants — only `transform`/`filter`/
51
+ // `will-change` do. In practice we observed the global-graph modal
52
+ // rendering as a narrow vertical strip instead of its expected 80vw ×
53
+ // 80vh centered panel, and the stock search container failing to center
54
+ // despite a `display: flex; justify-content: center` override. Both
55
+ // symptoms vanish when the body isolation rule is removed, so we drop
56
+ // it here. If a future feature re-adds a body-level pseudo-element with
57
+ // negative z-index, the lock can come back at the same time.
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // 2. Hairline column rule between left sidebar and article column
61
+ // ---------------------------------------------------------------------------
62
+ //
63
+ // Quartz's stock layout (`base.scss`) lays out `.sidebar.left` /
64
+ // `.center` / `.sidebar.right` on a CSS grid with a 5px column gap.
65
+ // A plain `border-right` on `.sidebar.left` would technically work,
66
+ // but the sidebar carries `padding: $topSpacing 2rem 2rem 2rem`, so
67
+ // the border would sit at the right edge of the *padding box* —
68
+ // flush with the column gap. Visually fine; semantically a border
69
+ // belongs to a single element, not the gutter.
70
+ //
71
+ // The pseudo-element approach decouples the rule from the sidebar's
72
+ // box model: it sits as a paint-only sibling that lives in the
73
+ // gutter region without participating in layout. This also makes it
74
+ // trivial to gate by viewport (we only want the rule when there's
75
+ // actually a sidebar-and-center side-by-side layout).
76
+ //
77
+ // Mobile guard: the mobile layout collapses the sidebar to a
78
+ // horizontal row at the top of the page (see `base.scss` line
79
+ // 235-244 — `.sidebar.left @media all and ($mobile)`). A vertical
80
+ // rule there reads as an out-of-place glyph, so we gate the rule
81
+ // on `(min-width: 800px)` to match Quartz's `$mobile` breakpoint.
82
+
83
+ @media (min-width: 800px) {
84
+ .sidebar.left::after {
85
+ content: "";
86
+ position: absolute;
87
+ top: 0;
88
+ bottom: 0;
89
+ right: 0;
90
+ width: 1px;
91
+ background: var(--rule);
92
+ pointer-events: none;
93
+ }
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // 3. Hairline section break — `<hr>` renders as a flat rule
98
+ // ---------------------------------------------------------------------------
99
+ //
100
+ // Markdown's `---` becomes `<hr>` in the rendered article. We replace
101
+ // the browser default with a clean 1px hairline in `--rule`. Tech
102
+ // aesthetic: a divider is just a divider, no ornament.
103
+ //
104
+ // Scoped to `article hr` so it only fires inside the article column.
105
+ // Sidebar/footer hrs — if any ever appear — keep their default
106
+ // rendering.
107
+
108
+ article hr {
109
+ border: 0;
110
+ height: 1px;
111
+ background: var(--rule);
112
+ margin: 2.5rem 0;
113
+ }