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,142 @@
1
+ // Brain Explorer extras — P4.2 of the Wiki UX Overhaul.
2
+ //
3
+ // This file is installed at
4
+ // `<vault>/.quartz/quartz/styles/brain/_explorer.scss` by `brain vault
5
+ // render --overlay` and pulled into the global SCSS via `custom.scss`'s
6
+ // `@use "./brain/explorer"` line. Loaded after `_sidebar.scss` so any
7
+ // sticky-header / surface-tier rules in the sidebar partial paint
8
+ // underneath these explorer-content children.
9
+ //
10
+ // Scope of this file:
11
+ //
12
+ // * `.brain-explorer-ingested-toggle` — the "Show ingested" /
13
+ // "Hide ingested" pill that the inline script injects as the
14
+ // first child of `.explorer-content`. Drives state via
15
+ // `[data-active="true"]` (set by the inline script on toggle).
16
+ // * `.brain-explorer-month-header` — month-group header `<li>`
17
+ // injected by the P4.3 `buildMonthGroupedChildren` pass when
18
+ // descending into `_ingested/krisp/` or `_ingested/gmail/`.
19
+ // * `.brain-explorer-month-date` / `.brain-explorer-month-title` —
20
+ // the two-span structure inside each grouped file's link, so
21
+ // the date prefix can render in dimmed tabular numerals while
22
+ // the title keeps the regular ink color.
23
+ //
24
+ // Scope NOT covered here:
25
+ //
26
+ // * Folder-count badges, explorer indent guides, sticky search
27
+ // header — owned by `_sidebar.scss` (Lane C).
28
+ // * The `<ul class="explorer-ul">` tree itself — owned by upstream
29
+ // `quartz/components/styles/explorer.scss`.
30
+ //
31
+ // Tokens consumed (all from `_tokens.scss`):
32
+ //
33
+ // * `--surface-1` — toggle background when inactive
34
+ // * `--accent` — toggle bg when active
35
+ // * `--rule` — toggle border
36
+ // * `--ink-1` / `--ink-2` — text colors
37
+ // * `--motion-fast` — hover transition
38
+
39
+ .explorer .brain-explorer-ingested-toggle {
40
+ // Layout — full-width pill button so it reads as a control rail
41
+ // rather than a tag-pill. Sits above the tree but inside the
42
+ // collapsible explorer-content region.
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ width: 100%;
47
+ margin: 0 0 0.5rem 0;
48
+ padding: 0.3rem 0.6rem;
49
+ font: inherit;
50
+ font-size: 0.75rem;
51
+ letter-spacing: 0.04em;
52
+ // brain: lowercase by default so the chip reads "show ingested"
53
+ // not "SHOW INGESTED" — matches the P3.4 lowercase-tag contract.
54
+ text-transform: none;
55
+ cursor: pointer;
56
+ background: transparent;
57
+ color: var(--ink-2, var(--gray));
58
+ border: 1px solid var(--rule, var(--lightgray));
59
+ border-radius: 999px;
60
+ transition:
61
+ background var(--motion-fast, 120ms) ease,
62
+ color var(--motion-fast, 120ms) ease,
63
+ border-color var(--motion-fast, 120ms) ease;
64
+
65
+ &:hover,
66
+ &:focus-visible {
67
+ background: var(--accent-soft, var(--highlight));
68
+ color: var(--ink-1, var(--darkgray));
69
+ border-color: var(--accent, var(--secondary));
70
+ outline: none;
71
+ }
72
+
73
+ // Active state — toggle ON, ingested visible. Reverse fg/bg so
74
+ // the active read matches the pattern used by the P3.2 search
75
+ // chips and the graph-view filter chips.
76
+ &[data-active="true"] {
77
+ background: var(--accent, var(--secondary));
78
+ color: var(--light, white);
79
+ border-color: var(--accent, var(--secondary));
80
+
81
+ &:hover,
82
+ &:focus-visible {
83
+ // Slight darken on hover so the active chip still gives
84
+ // pressed-state feedback.
85
+ background: color-mix(in srgb, var(--accent, var(--secondary)) 85%, black);
86
+ }
87
+ }
88
+ }
89
+
90
+ // ---------------------------------------------------------------------
91
+ // P4.3 — Per-source month grouping inside `_ingested/{krisp,gmail}/`.
92
+ // The inline script (`buildMonthGroupedChildren`) injects a header
93
+ // `<li>` per month plus rewrites each file's link text into a
94
+ // `<span.brain-explorer-month-date> · <span.brain-explorer-month-title>`
95
+ // pair. Styling lives here so the upstream `explorer.scss` partial
96
+ // stays unforked.
97
+ // ---------------------------------------------------------------------
98
+
99
+ .explorer .brain-explorer-month-header {
100
+ // Appearance — dimmed small-caps section heading that reads as a
101
+ // separator rather than as a navigable row. No hover/active state
102
+ // because the header is non-interactive (`role="presentation"`).
103
+ list-style: none;
104
+ margin: 0.6rem 0 0.15rem 0;
105
+ padding: 0.1rem 0;
106
+ font-family: var(--bodyFont, inherit);
107
+ font-size: 0.7rem;
108
+ letter-spacing: 0.06em;
109
+ text-transform: uppercase;
110
+ color: var(--ink-3, var(--gray));
111
+ // Subtle hairline below the label so the eye picks up the section
112
+ // boundary without competing with the file rows.
113
+ border-bottom: 1px solid var(--rule, var(--lightgray));
114
+ // First header in a folder-outer flush against the top — drop the
115
+ // top margin so it doesn't collide with the parent folder's
116
+ // padding/indent.
117
+ &:first-child {
118
+ margin-top: 0.1rem;
119
+ }
120
+ }
121
+
122
+ .explorer .brain-explorer-month-date {
123
+ // Tabular numerals so `Mar 9` / `Apr 15` line up vertically when
124
+ // stacked. Lighter weight + dimmed color so the date prefix feels
125
+ // like marginalia, not the primary read.
126
+ display: inline-block;
127
+ min-width: 2.6rem;
128
+ margin-right: 0.25rem;
129
+ font-variant-numeric: tabular-nums;
130
+ font-feature-settings: "tnum" 1;
131
+ font-size: 0.78rem;
132
+ color: var(--ink-3, var(--gray));
133
+ white-space: nowrap;
134
+ }
135
+
136
+ .explorer .brain-explorer-month-title {
137
+ // The title carries the regular ink color so it remains the primary
138
+ // read in the row. No layout overrides — let the link's own padding
139
+ // and ellipsis-truncation rules from the upstream `explorer.scss`
140
+ // continue to apply.
141
+ color: inherit;
142
+ }
@@ -0,0 +1,182 @@
1
+ // Brain home page card grid — Lane C of the 2026 commonplace-book redesign.
2
+ //
3
+ // This file is installed at
4
+ // `<vault>/.quartz/quartz/styles/brain/_home.scss` by `brain vault
5
+ // render --overlay` and pulled into the global SCSS via `custom.scss`'s
6
+ // `@use "./brain/home"` line.
7
+ //
8
+ // Scope of this file:
9
+ //
10
+ // * Detect the home page via `body[data-slug="index"]` (Quartz
11
+ // stamps the slug onto `<body>` from `renderPage.tsx`).
12
+ // * Inside the article body's top-level `<ul>` lists, render the
13
+ // direct child `<li>` items as a responsive card grid.
14
+ // * Cards: `--surface-1` background, `--shadow-md` on hover, accent
15
+ // border on hover, generous padding.
16
+ // * Wiki + ingested links INSIDE a card opt out of Lane B's
17
+ // underline + box-shadow source rail — the card itself IS the
18
+ // affordance, so the inner link should read as plain text.
19
+ //
20
+ // CSS-only — no markdown change. Degrades to a vertical list cleanly
21
+ // when the assumed `<ul>` shape isn't present (for a future home doc
22
+ // that uses different markup).
23
+ //
24
+ // Tokens consumed (from `_tokens.scss`):
25
+ // * `--surface-1` — card background
26
+ // * `--shadow-sm` / `--shadow-md` — resting + hover elevation
27
+ // * `--rule` — card border (resting)
28
+ // * `--accent-strong` — card border on hover
29
+ // * `--ink-0` / `--ink-1` / `--ink-2` — text + supporting copy
30
+ // * `--motion-fast` / `--motion-mid` / `--motion-easing` — transitions
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // 1. Card grid layout — only on the home page
34
+ // ---------------------------------------------------------------------------
35
+ //
36
+ // The home `index.md` is structured as `<h2>` section headings followed
37
+ // by `<ul>` lists where each `<li>` contains a wiki/ingested link plus
38
+ // a description (`<a>...</a> — text`). We turn each top-level `<ul>`
39
+ // into a CSS grid, with each `<li>` becoming a card.
40
+ //
41
+ // Why not target h2-anchored sections individually: h2 + ul is a
42
+ // repeated pattern (Doors, Topic clusters, etc.), and they all share
43
+ // the same card treatment. Repainting every `<ul>` inside the article
44
+ // is simpler and stays consistent if more sections land later.
45
+ //
46
+ // Why CSS-only: the plan flagged this explicitly — markdown stays
47
+ // human-edited and unsurprising; no markdown rewrite, no JS dependency.
48
+ // The grid degrades to a vertical list (the default `<ul>` rendering)
49
+ // in any browser that ignores `display: grid`.
50
+
51
+ body[data-slug="index"] {
52
+ article.popover-hint > ul,
53
+ // brain: also catch nested .popover-hint wrappers (Quartz's article
54
+ // body is `<article class="popover-hint">`, but we name both forms
55
+ // for resilience against future renderPage.tsx changes).
56
+ article > ul {
57
+ list-style: none;
58
+ padding: 0;
59
+ margin: 0.6rem 0 1.6rem 0;
60
+
61
+ // Responsive grid: 3 columns at desktop, 2 at tablet, 1 below.
62
+ // `auto-fit` + `minmax(240px, 1fr)` would also work but explicit
63
+ // breakpoints match the plan's "3/2/1" spec exactly and read
64
+ // clearly in devtools.
65
+ display: grid;
66
+ gap: 0.8rem;
67
+ grid-template-columns: 1fr;
68
+
69
+ @media (min-width: 768px) {
70
+ grid-template-columns: repeat(2, 1fr);
71
+ }
72
+ @media (min-width: 1024px) {
73
+ grid-template-columns: repeat(3, 1fr);
74
+ }
75
+
76
+ // Each list item becomes a card.
77
+ > li {
78
+ box-sizing: border-box;
79
+ position: relative;
80
+ background: var(--surface-1);
81
+ border: 1px solid var(--rule);
82
+ border-radius: 8px;
83
+ padding: 1.2rem;
84
+ box-shadow: var(--shadow-sm);
85
+ color: var(--ink-1);
86
+ line-height: 1.5;
87
+ list-style: none;
88
+ cursor: pointer;
89
+ transition:
90
+ transform var(--motion-mid) var(--motion-easing),
91
+ box-shadow var(--motion-mid) var(--motion-easing),
92
+ border-color var(--motion-mid) var(--motion-easing);
93
+
94
+ &:hover {
95
+ transform: translateY(-2px);
96
+ box-shadow: var(--shadow-md);
97
+ border-color: var(--accent-strong);
98
+ }
99
+
100
+ // The leading wiki/ingested link inside the card reads as the
101
+ // card title — promote its visual weight.
102
+ //
103
+ // Stretched-link pattern: the leading `<a>` paints an invisible
104
+ // `::before` overlay across the entire card so a click anywhere
105
+ // in the card follows the link. Markdown stays as `<li>emoji
106
+ // <a>title</a> — desc</li>` (no markup change), and the link's
107
+ // accessible name still comes from its text. Tradeoff: text
108
+ // selection inside the card requires shift-click (same as
109
+ // Bootstrap's `.stretched-link`); acceptable here because the
110
+ // card's primary affordance is "navigate", not "read & copy".
111
+ > a:first-child {
112
+ display: inline;
113
+ font-family: var(--displayFont);
114
+ font-size: var(--fs-md);
115
+ font-weight: 600;
116
+ color: var(--ink-0);
117
+ margin-right: 0.25em;
118
+
119
+ &::before {
120
+ content: "";
121
+ position: absolute;
122
+ inset: 0;
123
+ border-radius: inherit;
124
+ z-index: 1;
125
+ }
126
+ }
127
+
128
+ // Tag chips inside a card stay independently clickable — float
129
+ // them above the stretched-link overlay so the chip's own click
130
+ // target wins over the card-wide link.
131
+ a.tag-link {
132
+ position: relative;
133
+ z-index: 2;
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // 2. Opt cards out of Lane B link-kind chrome
141
+ // ---------------------------------------------------------------------------
142
+ //
143
+ // Lane B's `_links.scss` paints `[data-brain-link-kind="wiki"]` with a
144
+ // terracotta underline + the `a.internal` reset, and
145
+ // `[data-brain-link-kind="ingested"]` with the source-tinted left
146
+ // rail. Inside a card, both treatments compete with the card's own
147
+ // hover affordance — the card lifts, that's the affordance, the
148
+ // link should read as plain card title text.
149
+ //
150
+ // Higher specificity than Lane B (adds the `body[data-slug="index"]`
151
+ // + `> li > a` chain) so this override wins without `!important`.
152
+ //
153
+ // brain (Lane C audit fix 2026-05-02): exclude `.tag-link` from the
154
+ // reset. Tag chips (rendered by `Component.TagList` and inline
155
+ // `[[tags/x]]` wiki-links) carry both `class="internal"` AND
156
+ // `class="tag-link"`. Without the `:not(.tag-link)` filter the
157
+ // reset blew away Lane B's pill chrome (background, border-radius,
158
+ // padding) — leaving a bare-text "tag" in the card. The chip *is*
159
+ // the affordance, the card lift is complementary; both should
160
+ // survive.
161
+
162
+ body[data-slug="index"] article ul > li > a[data-brain-link-kind="wiki"]:not(.tag-link),
163
+ body[data-slug="index"] article ul > li > a[data-brain-link-kind="ingested"]:not(.tag-link),
164
+ body[data-slug="index"] article ul > li > a.internal:not(.tag-link) {
165
+ text-decoration: none;
166
+ background: transparent;
167
+ border-radius: 0;
168
+ padding: 0;
169
+ // Source-rail box-shadow reset for ingested kind.
170
+ box-shadow: none;
171
+
172
+ &:hover {
173
+ text-decoration-thickness: 0;
174
+ box-shadow: none;
175
+ }
176
+ }
177
+
178
+ // Tag-link chips inside cards stay as chips (the chip *is* the
179
+ // affordance, and the card's hover lift complements rather than
180
+ // competes with the chip's own micro-interaction). The
181
+ // `:not(.tag-link)` filter on the reset above is what preserves
182
+ // them — without it Lane B's pill chrome would be wiped.
@@ -0,0 +1,322 @@
1
+ // Brain wiki — link type system styles (Lane B, the headline complaint).
2
+ //
3
+ // This file is installed at
4
+ // `<vault>/.quartz/quartz/styles/brain/_links.scss` by `brain vault
5
+ // render --overlay` and pulled into the global SCSS via
6
+ // `custom.scss`'s `@use "./brain/links"` line.
7
+ //
8
+ // Five distinct visual treatments — one per link kind. The
9
+ // `data-brain-link-kind` attribute is stamped at build time by the
10
+ // `linkKindMark.ts` transformer, which keys off the URL pattern
11
+ // (and the `data-brain-derived` attribute already stamped by
12
+ // `derivedFenceMark.ts` for evidence edges). For ingested links we
13
+ // also read `data-brain-source` (krisp/slack/gmail/manual), which is
14
+ // stamped at runtime by `linkSourceTag.js` because the source segment
15
+ // lives in the URL path but extracting it at build time would require
16
+ // a per-doc lookup.
17
+ //
18
+ // Token contract — every color, motion timing, and surface here is
19
+ // pulled from `_tokens.scss` so a future palette swap propagates
20
+ // automatically. Tokens consumed:
21
+ //
22
+ // * `--accent-soft`, `--accent-strong` — wiki underline + tag-chip hover
23
+ // * `--ink-2` — external dotted underline + derived dashed underline
24
+ // * `--surface-2` — tag-chip resting background
25
+ // * `--motion-fast`, `--motion-easing` — universal hover transitions
26
+ // * `--brain-source-{krisp,slack,gmail,manual}` — ingested left-rail color
27
+ //
28
+ // Underline pattern — uses `text-decoration-thickness` +
29
+ // `text-underline-offset` (2020+ CSS) instead of `border-bottom` so:
30
+ // * Descenders (`p`, `g`, `y`, `j`) aren't clipped by the rule.
31
+ // * Underline thickness scales with `font-size` automatically.
32
+ // * The underline can be transitioned (`text-decoration-thickness`
33
+ // is animatable in modern browsers; `border-bottom-width` is too,
34
+ // but border-bottom adds layout-shift risk via the box model).
35
+ //
36
+ // Selector strategy — every rule keys off `[data-brain-link-kind=...]`
37
+ // rather than `.internal` / `.external` / `.tag-link` so the styling
38
+ // is decoupled from Quartz's internal class naming. Stock Quartz
39
+ // classes survive (we don't strip them) but they aren't used as
40
+ // selectors here.
41
+ //
42
+ // brain a11y note: screen readers (NVDA / JAWS / VoiceOver / Chromium-
43
+ // derived) WILL announce the "↗" content of the external-link
44
+ // `::after` rule — CSS pseudo-element `content` is NOT auto-aria-
45
+ // hidden, contrary to the common myth. Stock Quartz's SVG icon had
46
+ // `aria-hidden="true"` for this reason; once we disable it (in
47
+ // `quartz.config.ts` via `externalLinkIcon: false`, to avoid double-
48
+ // arrow rendering with the `::after` glyph), the announcement
49
+ // returns. Accepted tradeoff — the alternative is reverting to
50
+ // stock's SVG and losing CSS color/opacity/transition control over
51
+ // the indicator. Revisit if a11y feedback surfaces.
52
+
53
+ // Feature flag for the universal visited-state desaturation. Default
54
+ // on; flip to `false` if testing reveals the saturate(0.7) filter
55
+ // muddies the design or interacts poorly with a future color tweak.
56
+ // `!default` means a downstream `@use "./brain/links" with (...)`
57
+ // can override without editing this file.
58
+ $enable-visited-desaturation: true !default;
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // 1. wiki — internal links to other vault notes (the most common kind)
62
+ // ---------------------------------------------------------------------------
63
+ //
64
+ // Subtle terracotta underline with a small offset so descenders
65
+ // breathe. Hover thickens the underline and shifts to the full-
66
+ // strength accent — a tactile-feeling micro-interaction that signals
67
+ // "this is clickable" without shouting.
68
+ a[data-brain-link-kind="wiki"] {
69
+ // brain: stock Quartz `quartz/styles/base.scss` paints `a.internal`
70
+ // with `background-color: var(--highlight)`, `padding: 0 0.1rem`, and
71
+ // `border-radius: 5px` — a chunky 12%-alpha terracotta pill. Lane B's
72
+ // wiki kind has equal specificity (0,1,1) but doesn't redeclare those
73
+ // three properties, so the pill bleeds through. Reset them explicitly
74
+ // here so the redesign's subtle underline reads as intended.
75
+ background: transparent;
76
+ border-radius: 0;
77
+ padding: 0;
78
+ text-decoration: underline;
79
+ text-decoration-color: var(--accent-soft);
80
+ text-decoration-thickness: 1px;
81
+ text-underline-offset: 0.18em;
82
+ // brain: `text-decoration-skip-ink: auto` is the browser default in
83
+ // modern engines but spell it out so descenders are explicitly
84
+ // skipped (no underline crossing through `g`/`y`/`p`).
85
+ text-decoration-skip-ink: auto;
86
+ transition:
87
+ text-decoration-thickness var(--motion-fast) var(--motion-easing),
88
+ text-decoration-color var(--motion-fast) var(--motion-easing);
89
+
90
+ &:hover {
91
+ text-decoration-thickness: 2px;
92
+ text-decoration-color: var(--accent-strong);
93
+ }
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // 2. external — http(s)/mailto links that leave the vault
98
+ // ---------------------------------------------------------------------------
99
+ //
100
+ // Dotted underline in the muted ink color signals "different
101
+ // destination" without competing with wiki links for attention.
102
+ // The trailing `↗` arrow is the universal "leaves this site"
103
+ // affordance (Wikipedia, MDN, GOV.UK conventions).
104
+ a[data-brain-link-kind="external"] {
105
+ text-decoration: underline;
106
+ text-decoration-style: dotted;
107
+ text-decoration-color: var(--ink-2);
108
+ text-decoration-thickness: 1px;
109
+ text-underline-offset: 0.18em;
110
+ text-decoration-skip-ink: auto;
111
+ transition:
112
+ text-decoration-thickness var(--motion-fast) var(--motion-easing),
113
+ text-decoration-color var(--motion-fast) var(--motion-easing);
114
+
115
+ &:hover {
116
+ text-decoration-thickness: 2px;
117
+ text-decoration-color: var(--accent-strong);
118
+ }
119
+
120
+ // brain: external-link arrow indicator. Inline-baseline alignment
121
+ // keeps it riding on the text baseline; `font-size: 0.85em` shrinks
122
+ // it just enough to read as an icon, not a glyph.
123
+ &::after {
124
+ content: "↗";
125
+ font-size: 0.85em;
126
+ margin-left: 0.15em;
127
+ opacity: 0.7;
128
+ vertical-align: baseline;
129
+ // Don't underline the arrow itself (looks weird).
130
+ text-decoration: none;
131
+ display: inline-block;
132
+ }
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // 3. tag — `#tag` and `[[tags/x]]` links rendered as pill chips
137
+ // ---------------------------------------------------------------------------
138
+ //
139
+ // Pill-shape chip with a leading `#`, small-caps tracking. Hover
140
+ // lifts the chip 1px and tints the background — the chip becomes a
141
+ // small button. `inline-flex` aligns the chip's baseline with
142
+ // surrounding running text without needing magic margins.
143
+ //
144
+ // Selector covers BOTH:
145
+ // * `[data-brain-link-kind="tag"]` — stamped by `linkKindMark.ts`
146
+ // for tag links the markdown pipeline produces (e.g. `#tag`
147
+ // inline syntax via OFM, or `[[tags/foo]]` wiki-link form).
148
+ // * `.tag-link` — Quartz's stock class on tag chips emitted by the
149
+ // `TagList` component (article header) and the `TagPage` emitter
150
+ // (tag page chips). These are JSX/hast nodes, not mdast, so
151
+ // `linkKindMark` never sees them — we lean on the upstream class
152
+ // for them. Listed second so the data-attribute is the canonical
153
+ // selector when both are present (they're equivalent rules; the
154
+ // comma form just widens reach).
155
+ a[data-brain-link-kind="tag"],
156
+ a.tag-link {
157
+ display: inline-flex;
158
+ align-items: baseline;
159
+ padding: 0.1rem 0.55rem;
160
+ background: var(--surface-2);
161
+ border-radius: 999px;
162
+ // brain (Linear variant): drop `smcp` — Geist doesn't ship the
163
+ // small-caps OpenType feature, so the declaration is a silent no-op.
164
+ // Lining numerals + slight letter-spacing keep the chip readable
165
+ // without uppercasing the text.
166
+ //
167
+ // brain (P3.4 — Wiki UX Overhaul, item 10): `text-transform: none`
168
+ // (explicit) — tags are the canonical lowercased values produced by
169
+ // `brain.tags.normalize_tags` (casefold + hyphen-separated). The
170
+ // earlier `uppercase` rule made every chip render `#AGENTIC-AI`,
171
+ // which contradicted the storage format and made the visual feel
172
+ // shouty. Locking `text-transform: none` here also defends against
173
+ // a future upstream Quartz default that might flip the default to
174
+ // uppercase. The DOM text from `TagList.tsx` is already lowercase,
175
+ // so this rule is the single seam between normalized storage and
176
+ // the user-visible chip.
177
+ font-feature-settings: "lnum";
178
+ text-transform: none;
179
+ letter-spacing: 0.04em;
180
+ text-decoration: none;
181
+ // brain: vertical-align nudge so the chip sits on the text baseline
182
+ // even with the inline padding. Without this, the pill rides ~2px
183
+ // below the baseline when wrapped inside running serif text.
184
+ vertical-align: baseline;
185
+ transition:
186
+ background var(--motion-fast) var(--motion-easing),
187
+ transform var(--motion-fast) var(--motion-easing),
188
+ color var(--motion-fast) var(--motion-easing);
189
+
190
+ // brain: leading `#` — opacity 0.5 so it reads as decoration, not
191
+ // content. `margin-right: 0.1em` gives it just enough breathing room
192
+ // from the tag name.
193
+ &::before {
194
+ content: "#";
195
+ opacity: 0.5;
196
+ margin-right: 0.1em;
197
+ }
198
+
199
+ &:hover {
200
+ background: var(--accent-soft);
201
+ transform: translateY(-1px);
202
+ }
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // 4. derived — Phase D evidence edges (auto-generated "Related" links)
207
+ // ---------------------------------------------------------------------------
208
+ //
209
+ // Italic + dashed underline + opacity 0.78 says "this is a hint, not
210
+ // an authored claim." `cursor: help` reinforces the pattern (browsers
211
+ // render `?` cursor; users learn that hovering reveals the rule).
212
+ // The `title` attribute is already populated by `derivedFenceMark.ts`
213
+ // with the rule shape (`same_day_participant`, `shared_thread`).
214
+ a[data-brain-link-kind="derived"] {
215
+ text-decoration: underline;
216
+ text-decoration-style: dashed;
217
+ text-decoration-color: var(--ink-2);
218
+ text-decoration-thickness: 1px;
219
+ text-underline-offset: 0.18em;
220
+ text-decoration-skip-ink: auto;
221
+ font-style: italic;
222
+ opacity: 0.78;
223
+ cursor: help;
224
+ transition:
225
+ opacity var(--motion-fast) var(--motion-easing),
226
+ text-decoration-color var(--motion-fast) var(--motion-easing);
227
+
228
+ &:hover {
229
+ opacity: 1;
230
+ text-decoration-color: var(--accent-strong);
231
+ }
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // 5. ingested — vault-internal links into `_ingested/<source>/<slug>`
236
+ // ---------------------------------------------------------------------------
237
+ //
238
+ // Inherits the wiki underline pattern (terracotta thin → thick on
239
+ // hover) and adds a 2px source-tinted left rail via `box-shadow`
240
+ // (NOT `border-left` — `border-left` adds 2px to the box width and
241
+ // causes layout shift on the long form below; `inset` `box-shadow`
242
+ // paints inside the existing box so widths stay consistent).
243
+ //
244
+ // Source-color comes from a CSS custom property (`--source-color`)
245
+ // scoped per-link via the `[data-brain-source="..."]` selectors
246
+ // below. Default fallback is `--brain-source-manual` so any
247
+ // uncategorized ingested link still gets a rail.
248
+ a[data-brain-link-kind="ingested"] {
249
+ // brain: same reset as the wiki block — stock `a.internal` from
250
+ // `base.scss` paints a chunky 12%-alpha pill (background + padding +
251
+ // border-radius) that would mix with the source-tinted rail. Wipe
252
+ // background/border-radius and rebuild padding from scratch so the
253
+ // 0.35em rail offset is the only horizontal padding present.
254
+ background: transparent;
255
+ border-radius: 0;
256
+ // Padding rebuilt: zero on top/right/bottom (match wiki kind),
257
+ // 0.35em on the left to keep text off the source rail.
258
+ padding: 0 0 0 0.35em;
259
+ // Inherit wiki underline behaviour. SCSS doesn't have `extend`
260
+ // across the `[attr=value]` selector cleanly, so re-declare the
261
+ // five properties (kept in sync with the wiki block above by code
262
+ // review).
263
+ text-decoration: underline;
264
+ text-decoration-color: var(--accent-soft);
265
+ text-decoration-thickness: 1px;
266
+ text-underline-offset: 0.18em;
267
+ text-decoration-skip-ink: auto;
268
+ // The source-tinted rail. `inset` keeps it inside the box; the
269
+ // 2px width matches the wiki-link hover thickness so the visual
270
+ // weight is consistent between the two related kinds.
271
+ box-shadow: inset 2px 0 0 var(--source-color, var(--brain-source-manual));
272
+ transition:
273
+ text-decoration-thickness var(--motion-fast) var(--motion-easing),
274
+ text-decoration-color var(--motion-fast) var(--motion-easing),
275
+ box-shadow var(--motion-fast) var(--motion-easing);
276
+
277
+ &:hover {
278
+ text-decoration-thickness: 2px;
279
+ text-decoration-color: var(--accent-strong);
280
+ // Thicken the source rail on hover too — same "tactile" cue as
281
+ // the wiki underline grow.
282
+ box-shadow: inset 3px 0 0 var(--source-color, var(--brain-source-manual));
283
+ }
284
+
285
+ // Source palette mappings. Each rule sets `--source-color` to the
286
+ // matching `--brain-source-*` token from `_tokens.scss`. Both light
287
+ // and dark mode `--brain-source-*` definitions live in `_tokens.scss`,
288
+ // so no per-mode override is needed here.
289
+ &[data-brain-source="krisp"] {
290
+ --source-color: var(--brain-source-krisp);
291
+ }
292
+ &[data-brain-source="slack"] {
293
+ --source-color: var(--brain-source-slack);
294
+ }
295
+ &[data-brain-source="gmail"] {
296
+ --source-color: var(--brain-source-gmail);
297
+ }
298
+ &[data-brain-source="manual"] {
299
+ --source-color: var(--brain-source-manual);
300
+ }
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Universal: visited state
305
+ // ---------------------------------------------------------------------------
306
+ //
307
+ // `filter: saturate(0.7)` desaturates the link color slightly without
308
+ // overriding the kind-specific styling above (which uses
309
+ // `text-decoration-color`, not `color`, so a `color` override would
310
+ // be invisible anyway). Applies to every kind including chips and
311
+ // dashed derived links — visited state should read consistently
312
+ // across the whole link family.
313
+ //
314
+ // Gated by the SCSS variable at the top so it can be turned off from
315
+ // `custom.scss` via `@use "./brain/links" with ($enable-visited-desaturation: false)`
316
+ // if it ever clashes with another visual choice.
317
+ @if $enable-visited-desaturation {
318
+ a[data-brain-link-kind]:visited,
319
+ a.tag-link:visited {
320
+ filter: saturate(0.7);
321
+ }
322
+ }