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,161 @@
1
+ // Brain Search component — Phase 3.2 of the Wiki UX Overhaul.
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/components/Search.tsx` by `brain vault
5
+ // render --overlay`, OVERWRITING the stock Quartz `Search.tsx` so the
6
+ // component barrel (`components/index.ts`) re-exports the brain
7
+ // override instead of upstream. It does NOT compile or run from the
8
+ // brain repo itself — esbuild-loader inside the cloned Quartz
9
+ // workspace bundles `./scripts/search.inline` into a string that this
10
+ // component publishes via `Search.afterDOMLoaded`. The string is then
11
+ // injected at `</body>` time by Quartz's `renderPage.tsx`.
12
+ //
13
+ // Why this override exists: stock Quartz renders search results as
14
+ // `<h3>title</h3><p>snippet</p>` and offers no source filter. The brain
15
+ // corpus mixes ingested transcripts (krisp / slack / gmail) with
16
+ // authored notes (manual / vault), so a search popover that doesn't
17
+ // surface "where did this come from" makes results visually
18
+ // indistinguishable. P3.2 adds:
19
+ //
20
+ // * Source-icon prefix per result row — a one-glance visual key for
21
+ // gmail / krisp / slack / manual / vault.
22
+ // * Source filter chips above the input — toggle to constrain the
23
+ // visible results. State persists in `localStorage` under
24
+ // `brain.search.activeSources` so a user's preferred slice
25
+ // survives SPA navigation and full reloads.
26
+ // * Lazy preview pane — when a result is selected (hover / arrow),
27
+ // fetch `static/contentBodies/<slug>.json` (P3.1's split body file)
28
+ // and render the full content. P3.1 left only a 240-char snippet
29
+ // in `contentIndex.json`; without lazy fetching the preview pane
30
+ // would be permanently truncated.
31
+ // * Date column — short ISO date or "Nd ago" for the past week.
32
+ //
33
+ // The component layer (this file) only ships the markup + class hooks.
34
+ // All dynamic behaviour — chip state, lazy fetching, fuzzy match — is
35
+ // owned by the inline script at `./scripts/search.inline.ts`.
36
+ //
37
+ // brain: source-icon mapping. Pinned here at module scope so the same
38
+ // glyph table renders both inline (`renderResultRow` constant
39
+ // references it) and in the SSR'd chip rail (the script reads it at
40
+ // boot via the embedded JSON below). Unknown sources fall back to the
41
+ // vault glyph — better than rendering an empty span.
42
+ //
43
+ // Coordination point: if a future ingest source is added (e.g. brain
44
+ // grows a `notion` extractor), append it ONCE in
45
+ // `quartz_overrides/quartz/util/sourceIcons.ts` (the canonical
46
+ // SOURCE_ICONS table). This component, the inline search script, and
47
+ // the TagContent override all import from that one util — so the new
48
+ // glyph cascades to all three surfaces. Also append it to
49
+ // `_search.scss`'s chip palette (via the chip data attribute) AND to
50
+ // `commandPalette.inline.ts`'s `kindIcon()` helper if Cmd-K is rebuilt
51
+ // to share the same icon vocabulary.
52
+ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
53
+ // @ts-ignore — esbuild-loader rewrites this to a bundled string.
54
+ import script from "./scripts/search.inline"
55
+ import { classNames } from "../util/lang"
56
+ import { i18n } from "../i18n"
57
+ import { SOURCE_ICONS, SOURCE_CHIP_ORDER } from "../util/sourceIcons"
58
+
59
+ export interface SearchOptions {
60
+ enablePreview: boolean
61
+ }
62
+
63
+ const defaultOptions: SearchOptions = {
64
+ enablePreview: true,
65
+ }
66
+
67
+ // brain (P3.6 fix-4): source-icon table consolidated. Previously this
68
+ // file declared its own `SOURCE_ICONS` constant duplicated against
69
+ // `util/sourceIcons.ts` and `search.inline.ts`. All three sites now
70
+ // share the canonical util — adding a new source is a one-line change
71
+ // in `util/sourceIcons.ts`. The chip rail still serializes the table
72
+ // into `data-brain-source-icons` so the inline script reads it back at
73
+ // boot via `readSourceIcons` (defense in depth: even if the import
74
+ // chain breaks at runtime, the SSR'd attribute keeps the chip glyphs
75
+ // rendering).
76
+
77
+ // brain: ordered list of chip values rendered above the search input.
78
+ // Order is deterministic and pinned (vs. derived from the index) so
79
+ // the chip rail looks identical even when the loaded corpus is missing
80
+ // one source. Matches the `chipVocabularies.source` order in
81
+ // `graph.inline.ts` so the two filter rails read as siblings. Sourced
82
+ // from the shared `SOURCE_CHIP_ORDER` so the order stays in lock-step
83
+ // with the canonical util.
84
+ const CHIP_VALUES: ReadonlyArray<keyof typeof SOURCE_ICONS> = SOURCE_CHIP_ORDER
85
+
86
+ export default ((userOpts?: Partial<SearchOptions>) => {
87
+ const Search: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
88
+ const opts = { ...defaultOptions, ...userOpts }
89
+ const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder
90
+ // brain: serialize the icon table once into a data attribute so the
91
+ // inline script can rebuild the chip glyphs without a duplicate
92
+ // hard-coded copy. JSON.stringify keeps the table escapable through
93
+ // the HTML attribute boundary.
94
+ const iconsJson = JSON.stringify(SOURCE_ICONS)
95
+ return (
96
+ <div class={classNames(displayClass, "search")}>
97
+ <button class="search-button">
98
+ <svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7">
99
+ <title>Search</title>
100
+ <g class="search-path" fill="none">
101
+ <path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" />
102
+ <circle cx="8" cy="8" r="7" />
103
+ </g>
104
+ </svg>
105
+ <p>{i18n(cfg.locale).components.search.title}</p>
106
+ </button>
107
+ <div class="search-container">
108
+ <div class="search-space">
109
+ {/* brain: chip rail above the input. The script attaches
110
+ click handlers and reads the icon table from the
111
+ `data-brain-source-icons` attribute on the rail's root.
112
+ `data-active="true"` is reflected by the script when
113
+ a chip is in the active set; CSS keys hover/focus
114
+ styling off the same attribute. */}
115
+ <div
116
+ class="brain-search-chips"
117
+ role="group"
118
+ aria-label="Filter by source"
119
+ data-brain-source-icons={iconsJson}
120
+ >
121
+ <button
122
+ type="button"
123
+ class="brain-search-chip brain-search-chip-all"
124
+ data-brain-source="__all__"
125
+ data-active="true"
126
+ >
127
+ All
128
+ </button>
129
+ {CHIP_VALUES.map((value) => (
130
+ <button
131
+ type="button"
132
+ class="brain-search-chip"
133
+ data-brain-source={value}
134
+ data-active="true"
135
+ >
136
+ <span class="brain-search-chip-icon" aria-hidden="true">
137
+ {SOURCE_ICONS[value]}
138
+ </span>
139
+ <span class="brain-search-chip-label">{value}</span>
140
+ </button>
141
+ ))}
142
+ </div>
143
+ <input
144
+ autocomplete="off"
145
+ class="search-bar"
146
+ name="search"
147
+ type="text"
148
+ aria-label={searchPlaceholder}
149
+ placeholder={searchPlaceholder}
150
+ />
151
+ <div class="search-layout" data-preview={opts.enablePreview}></div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ )
156
+ }
157
+
158
+ Search.afterDOMLoaded = script
159
+
160
+ return Search
161
+ }) satisfies QuartzComponentConstructor
@@ -0,0 +1,72 @@
1
+ // Brain summary-lede component — wave Q2-SUMMARY-WIKI.
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/components/SummaryLede.tsx` by `brain vault
5
+ // render --overlay`. It does NOT compile or run from the brain repo
6
+ // itself; the imports below resolve against the dependencies Quartz
7
+ // pulls into the cloned workspace via `npm install`, not against any
8
+ // package brain ships.
9
+ //
10
+ // Tested against Quartz v4.5.x (April 2026). The component shape
11
+ // mirrors stock Quartz components — see `Backlinks.tsx` (also a
12
+ // brain-overlayed component) for the canonical pattern. If a future
13
+ // Quartz version restructures `QuartzComponent` / `QuartzComponentProps`,
14
+ // pull the latest reference component from
15
+ // https://github.com/jackyzha0/quartz/blob/v4/quartz/components/
16
+ // and re-apply the brain tweaks below.
17
+ //
18
+ // Strategy — render `fileData.frontmatter.summary` as an inline TL;DR
19
+ // above the article body. The Q1-D ``OllamaEnricher`` writes
20
+ // ``documents.summary``; the Q2 vault export pipeline plumbs that
21
+ // value into ``summary:`` frontmatter on the mirror file. This
22
+ // component reads that frontmatter key and renders an `<aside>` block
23
+ // with a soft "AI summary" eyebrow.
24
+ //
25
+ // When the frontmatter doesn't carry a ``summary`` (vault-tier notes
26
+ // the enricher hasn't touched, or short docs below the
27
+ // ``BRAIN_ENRICH_MIN_TOKENS`` threshold), the component renders
28
+ // `null` — no empty aside, no visual leak.
29
+ //
30
+ // Pin: this component is registered in `quartz.layout.ts` under
31
+ // `defaultContentPageLayout.beforeBody`, immediately after
32
+ // `Component.TagList()` and before `Component.Breadcrumbs()`. That
33
+ // places the lede above the article body but below the title /
34
+ // content meta strip, which is the natural place for a TL;DR.
35
+ //
36
+ // Responsibility (CLAUDE.md rule 8): this file owns the Preact
37
+ // component wrapper. Visual rules live in
38
+ // `../styles/brain/_summary_lede.scss`. No inline script — the lede
39
+ // is a static SSR'd block with no interactive behavior.
40
+
41
+ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
42
+ import { classNames } from "../util/lang"
43
+
44
+ const SummaryLede: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
45
+ // brain: defensive type-narrowing — Quartz's `fileData.frontmatter`
46
+ // is typed as `Record<string, unknown>` because authors can write
47
+ // arbitrary YAML. Only a string `summary` is renderable; a list,
48
+ // object, or null falls through to `null` (no aside).
49
+ const raw = fileData.frontmatter?.summary
50
+ if (typeof raw !== "string") {
51
+ return null
52
+ }
53
+ const summary = raw.trim()
54
+ if (summary.length === 0) {
55
+ return null
56
+ }
57
+
58
+ return (
59
+ <aside class={classNames(displayClass, "brain-summary-lede")} aria-label="AI summary">
60
+ <span class="brain-summary-lede-eyebrow" aria-hidden="true">
61
+ AI summary
62
+ </span>
63
+ <p class="brain-summary-lede-body">{summary}</p>
64
+ </aside>
65
+ )
66
+ }
67
+
68
+ // brain: no component-css here — visuals live in the global
69
+ // `_summary_lede.scss`, which is `@use`-imported via `custom.scss`. That
70
+ // ensures the styles ship with EVERY page in the build.
71
+
72
+ export default (() => SummaryLede) satisfies QuartzComponentConstructor
@@ -0,0 +1,92 @@
1
+ // Brain wiki — components barrel re-export with brain extensions wired in.
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/components/index.ts` by `brain vault render
5
+ // --overlay`, OVERWRITING the stock Quartz barrel. The overlay is a
6
+ // 1:1 file copy (`src/brain/vault/quartz_overlay.py`), so this is
7
+ // the canonical seam for ensuring the brain-extension component
8
+ // files in this directory get re-exported under the
9
+ // `Component.*` namespace consumed by `quartz.layout.ts`.
10
+ //
11
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
12
+ // version adds a new stock component, append its export below; the
13
+ // ones that already exist are sourced verbatim from
14
+ // https://github.com/jackyzha0/quartz/blob/v4/quartz/components/index.ts
15
+ // and the brain extensions are appended at the bottom in their own
16
+ // block.
17
+ //
18
+ // brain: stock Quartz component imports + exports — keep in lock-step
19
+ // with the upstream `index.ts` linked above. If you upgrade Quartz,
20
+ // diff upstream against this block and apply additions.
21
+ import Content from "./pages/Content"
22
+ import TagContent from "./pages/TagContent"
23
+ import FolderContent from "./pages/FolderContent"
24
+ import NotFound from "./pages/404"
25
+ import ArticleTitle from "./ArticleTitle"
26
+ import Darkmode from "./Darkmode"
27
+ import ReaderMode from "./ReaderMode"
28
+ import Head from "./Head"
29
+ import PageTitle from "./PageTitle"
30
+ import ContentMeta from "./ContentMeta"
31
+ import Spacer from "./Spacer"
32
+ import TableOfContents from "./TableOfContents"
33
+ import Explorer from "./Explorer"
34
+ import TagList from "./TagList"
35
+ import Graph from "./Graph"
36
+ import Backlinks from "./Backlinks"
37
+ import Search from "./Search"
38
+ import Footer from "./Footer"
39
+ import DesktopOnly from "./DesktopOnly"
40
+ import MobileOnly from "./MobileOnly"
41
+ import RecentNotes from "./RecentNotes"
42
+ import Breadcrumbs from "./Breadcrumbs"
43
+ import Comments from "./Comments"
44
+ import Flex from "./Flex"
45
+ import ConditionalRender from "./ConditionalRender"
46
+
47
+ // brain-extension: brain-only components added by the overlay. Each
48
+ // is documented in its own file's top-of-file comment.
49
+ // * CommandPalette — Lane C redesign — Cmd/Ctrl-P palette modal.
50
+ // Renders hidden markup once globally (registered in
51
+ // `quartz.layout.ts` `afterBody`); the inline script
52
+ // `scripts/commandPalette.inline.ts` owns the open/close + fuzzy
53
+ // search lifecycle.
54
+ import CommandPalette from "./CommandPalette"
55
+ import RelatedDocs from "./RelatedDocs"
56
+ // * SummaryLede — wave Q2-SUMMARY-WIKI — renders the auto-summary
57
+ // stored in `frontmatter.summary` (sourced from Q1-D
58
+ // `documents.summary`) as a TL;DR block above the article body.
59
+ // Pin: `defaultContentPageLayout.beforeBody` slot.
60
+ import SummaryLede from "./SummaryLede"
61
+
62
+ export {
63
+ ArticleTitle,
64
+ Content,
65
+ TagContent,
66
+ FolderContent,
67
+ Darkmode,
68
+ ReaderMode,
69
+ Head,
70
+ PageTitle,
71
+ ContentMeta,
72
+ Spacer,
73
+ TableOfContents,
74
+ Explorer,
75
+ TagList,
76
+ Graph,
77
+ Backlinks,
78
+ Search,
79
+ Footer,
80
+ DesktopOnly,
81
+ MobileOnly,
82
+ RecentNotes,
83
+ NotFound,
84
+ Breadcrumbs,
85
+ Comments,
86
+ Flex,
87
+ ConditionalRender,
88
+ // brain-extension exports
89
+ CommandPalette,
90
+ RelatedDocs,
91
+ SummaryLede,
92
+ }
@@ -0,0 +1,272 @@
1
+ // Brain TagContent — Phase 3.3 of the Wiki UX Overhaul.
2
+ //
3
+ // This file is a TEMPLATE. It is installed at
4
+ // `<vault>/.quartz/quartz/components/pages/TagContent.tsx` by `brain
5
+ // vault render --overlay`, OVERWRITING the stock Quartz page renderer
6
+ // for tag listing pages. It does NOT compile or run from the brain
7
+ // repo itself; imports are resolved by the cloned Quartz workspace.
8
+ //
9
+ // Why this override exists: stock Quartz's tag pages render via
10
+ // `<PageList>` which produces a flat list with date + title + tag
11
+ // pills. The brain corpus mixes ingested transcripts (krisp / slack /
12
+ // gmail) with authored notes (manual / vault), so a tag listing that
13
+ // doesn't surface the source is visually indistinguishable. P3.3
14
+ // adds:
15
+ //
16
+ // * Source-icon prefix per row — same vocabulary as the search
17
+ // popover (krisp 🎙️ / slack 💬 / gmail 📧 / manual ✍️ / vault 🌱).
18
+ // * 1-line snippet around the first occurrence of the tag in the
19
+ // doc's auto-generated description (falls back to the leading
20
+ // 200 chars when the tag word is not present in the description).
21
+ // * Explicit `tagged: #<tag1> #<tag2>` footer enumerating all the
22
+ // doc's tags — replaces the upstream tag-pill cluster with a
23
+ // single denser footer line.
24
+ //
25
+ // Upstream's index-mode (the `tags === "/"` overview page listing all
26
+ // tags) is preserved verbatim — only the per-tag listing path is
27
+ // overridden. The brain customisation is the rendering of each doc
28
+ // row inside the `else` branch.
29
+ //
30
+ // Tested against Quartz v4.5.x (April 2026). If a future Quartz
31
+ // version restructures the `TagContent` component (e.g. moves the
32
+ // per-tag rendering into its own file, or changes the
33
+ // `QuartzComponentProps` shape), pull the latest from
34
+ // https://github.com/jackyzha0/quartz/blob/v4/quartz/components/pages/TagContent.tsx
35
+ // and re-apply the brain delta marked with `// brain:` below.
36
+
37
+ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
38
+ import style from "../styles/listPage.scss"
39
+ import { Date as QuartzDate, getDate } from "../Date"
40
+ import { PageList, SortFn, byDateAndAlphabeticalFolderFirst } from "../PageList"
41
+ import { FullSlug, getAllSegmentPrefixes, resolveRelative, simplifySlug } from "../../util/path"
42
+ import { QuartzPluginData } from "../../plugins/vfile"
43
+ import { Root } from "hast"
44
+ import { htmlToJsx } from "../../util/jsx"
45
+ import { i18n } from "../../i18n"
46
+ import { ComponentChildren } from "preact"
47
+ import { concatenateResources } from "../../util/resources"
48
+ import { inferSource, sourceIconFor } from "../../util/sourceIcons"
49
+
50
+ interface TagContentOptions {
51
+ sort?: SortFn
52
+ numPages: number
53
+ // brain-extension: chars on either side of the first tag-occurrence
54
+ // in the description when constructing the per-row snippet. Pinned
55
+ // here so a future tweak (e.g. expanding to 240 chars to match the
56
+ // P3.1 contentIndex snippet budget) is a single-line change.
57
+ snippetWindow: number
58
+ }
59
+
60
+ const defaultOptions: TagContentOptions = {
61
+ numPages: 10,
62
+ snippetWindow: 100,
63
+ }
64
+
65
+ // brain: trim a single line of whitespace-collapsed text from ``description``
66
+ // centred on the first occurrence of ``tag``. When the tag word is not
67
+ // found we return the leading ``2 * window`` chars so the row still
68
+ // shows context. Whitespace is collapsed (multi-newline + tabs → single
69
+ // space) so the row stays a clean one-liner regardless of how the
70
+ // upstream description plugin built it.
71
+ //
72
+ // Match is case-insensitive on the tag's leaf segment (the part after
73
+ // the last `/` in nested tags like `interview/take-home`) — that's the
74
+ // word users actually expect to see highlighted in body text.
75
+ export function computeTagSnippet(
76
+ description: string,
77
+ tag: string,
78
+ window: number,
79
+ ): string {
80
+ const collapsed = description.replace(/\s+/g, " ").trim()
81
+ if (collapsed.length === 0) return ""
82
+ const leaf = tag.includes("/") ? tag.slice(tag.lastIndexOf("/") + 1) : tag
83
+ const idx = collapsed.toLowerCase().indexOf(leaf.toLowerCase())
84
+ if (idx < 0) {
85
+ return collapsed.slice(0, window * 2)
86
+ }
87
+ const start = Math.max(0, idx - window)
88
+ const end = Math.min(collapsed.length, idx + leaf.length + window)
89
+ const prefix = start > 0 ? "…" : ""
90
+ const suffix = end < collapsed.length ? "…" : ""
91
+ return `${prefix}${collapsed.slice(start, end)}${suffix}`
92
+ }
93
+
94
+ export default ((opts?: Partial<TagContentOptions>) => {
95
+ const options: TagContentOptions = { ...defaultOptions, ...opts }
96
+
97
+ const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
98
+ const { tree, fileData, allFiles, cfg } = props
99
+ const slug = fileData.slug
100
+
101
+ if (!(slug?.startsWith("tags/") || slug === "tags")) {
102
+ throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
103
+ }
104
+
105
+ const tag = simplifySlug(slug.slice("tags/".length) as FullSlug)
106
+ const allPagesWithTag = (t: string) =>
107
+ allFiles.filter((file) =>
108
+ (file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(t),
109
+ )
110
+
111
+ const content = (
112
+ (tree as Root).children.length === 0
113
+ ? fileData.description
114
+ : htmlToJsx(fileData.filePath!, tree)
115
+ ) as ComponentChildren
116
+ const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
117
+ const classes = cssClasses.join(" ")
118
+
119
+ if (tag === "/") {
120
+ // brain: index-mode (tags listing all tags) — preserved verbatim
121
+ // from upstream. The brain delta is only applied to per-tag
122
+ // pages where the row count is small enough to render the dense
123
+ // icon + snippet + footer markup without overwhelming the
124
+ // viewport. The aggregate index page lists every tag with a
125
+ // PageList preview underneath, where the upstream rendering is
126
+ // the right fit.
127
+ const tags = [
128
+ ...new Set(
129
+ allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
130
+ ),
131
+ ].sort((a, b) => a.localeCompare(b))
132
+ const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
133
+ for (const t of tags) {
134
+ tagItemMap.set(t, allPagesWithTag(t))
135
+ }
136
+ return (
137
+ <div class="popover-hint">
138
+ <article class={classes}>
139
+ <p>{content}</p>
140
+ </article>
141
+ <p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
142
+ <div>
143
+ {tags.map((t) => {
144
+ const pages = tagItemMap.get(t)!
145
+ const listProps = {
146
+ ...props,
147
+ allFiles: pages,
148
+ }
149
+
150
+ const contentPage = allFiles.filter((file) => file.slug === `tags/${t}`).at(0)
151
+
152
+ const root = contentPage?.htmlAst
153
+ const tagContent =
154
+ !root || root?.children.length === 0
155
+ ? contentPage?.description
156
+ : htmlToJsx(contentPage.filePath!, root)
157
+
158
+ const tagListingPage = `/tags/${t}` as FullSlug
159
+ const href = resolveRelative(fileData.slug!, tagListingPage)
160
+
161
+ return (
162
+ <div>
163
+ <h2>
164
+ <a class="internal tag-link" href={href}>
165
+ {t}
166
+ </a>
167
+ </h2>
168
+ {tagContent && <p>{tagContent}</p>}
169
+ <div class="page-listing">
170
+ <p>
171
+ {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
172
+ {pages.length > options.numPages && (
173
+ <>
174
+ {" "}
175
+ <span>
176
+ {i18n(cfg.locale).pages.tagContent.showingFirst({
177
+ count: options.numPages,
178
+ })}
179
+ </span>
180
+ </>
181
+ )}
182
+ </p>
183
+ <PageList limit={options.numPages} {...listProps} sort={options?.sort} />
184
+ </div>
185
+ </div>
186
+ )
187
+ })}
188
+ </div>
189
+ </div>
190
+ )
191
+ }
192
+
193
+ // brain: per-tag mode — replace the upstream `<PageList>` with a
194
+ // brain-styled row list. Sort uses the same default Quartz uses
195
+ // (`byDateAndAlphabeticalFolderFirst`) so the relative ordering
196
+ // matches user expectations (newest-first, undated trailing).
197
+ const sorter = options.sort ?? byDateAndAlphabeticalFolderFirst(cfg)
198
+ const pages = allPagesWithTag(tag).sort(sorter)
199
+
200
+ return (
201
+ <div class="popover-hint">
202
+ <article class={classes}>{content}</article>
203
+ <div class="page-listing">
204
+ <p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
205
+ <ul class="section-ul brain-tag-results">
206
+ {pages.map((page) => {
207
+ const title = page.frontmatter?.title ?? page.slug ?? ""
208
+ const tags = page.frontmatter?.tags ?? []
209
+ const fmSource =
210
+ typeof page.frontmatter?.source === "string"
211
+ ? (page.frontmatter.source as string)
212
+ : undefined
213
+ const source = inferSource(page.slug ?? "", fmSource)
214
+ const icon = sourceIconFor(source)
215
+ const description = page.description ?? ""
216
+ const snippet = computeTagSnippet(description, tag, options.snippetWindow)
217
+ const dateObj = getDate(cfg, page)
218
+ const href = resolveRelative(fileData.slug!, page.slug!)
219
+
220
+ return (
221
+ <li class="section-li brain-tag-row" data-brain-source={source}>
222
+ <a class="internal brain-tag-row-link" href={href}>
223
+ <span
224
+ class="brain-tag-icon"
225
+ aria-hidden="true"
226
+ data-brain-source={source}
227
+ >
228
+ {icon}
229
+ </span>
230
+ <span class="brain-tag-title">{title}</span>
231
+ {dateObj && (
232
+ <span class="brain-tag-date">
233
+ <QuartzDate date={dateObj} locale={cfg.locale} />
234
+ </span>
235
+ )}
236
+ {snippet && <span class="brain-tag-snippet">{snippet}</span>}
237
+ </a>
238
+ {tags.length > 0 && (
239
+ <p class="brain-tag-footer">
240
+ <span class="brain-tag-footer-label">tagged:</span>{" "}
241
+ {tags.map((rowTag, i) => (
242
+ // brain (P3.6 fix-6): bare `<>` fragments inside
243
+ // a `.map()` trip Preact's runtime warning
244
+ // ("Each child in a list should have a unique
245
+ // key prop"). Promote to a span with a stable
246
+ // `key={rowTag}` so the warning goes away and
247
+ // the reconciler can match nodes correctly when
248
+ // the tags list mutates between renders.
249
+ <span key={rowTag} class="brain-tag-footer-fragment">
250
+ {i > 0 ? " " : ""}
251
+ <a
252
+ class="internal tag-link brain-tag-footer-link"
253
+ href={resolveRelative(fileData.slug!, `tags/${rowTag}` as FullSlug)}
254
+ >
255
+ #{rowTag}
256
+ </a>
257
+ </span>
258
+ ))}
259
+ </p>
260
+ )}
261
+ </li>
262
+ )
263
+ })}
264
+ </ul>
265
+ </div>
266
+ </div>
267
+ )
268
+ }
269
+
270
+ TagContent.css = concatenateResources(style, PageList.css)
271
+ return TagContent
272
+ }) satisfies QuartzComponentConstructor