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,369 @@
1
+ // Brain command palette — Lane C of the 2026 commonplace-book redesign.
2
+ //
3
+ // This file is installed at
4
+ // `<vault>/.quartz/quartz/styles/brain/_command_palette.scss` by `brain vault
5
+ // render --overlay` and pulled into the global SCSS via `custom.scss`'s
6
+ // `@use "./brain/command_palette"` line (added between `_sidebar` and
7
+ // `_article`).
8
+ //
9
+ // Visual rules for the Cmd/Ctrl-P palette:
10
+ //
11
+ // * Native `<dialog>` reset — strip the browser's default
12
+ // `padding`, `border`, `position`, etc. so our modal styles
13
+ // control the chrome cleanly.
14
+ // * Native `::backdrop` pseudo-element — replaces the manual
15
+ // `.brain-cmdk-backdrop` div with the browser-managed scrim.
16
+ // Tinted with `var(--scrim)` from `_tokens.scss` (light/dark
17
+ // variants).
18
+ // * Modal — centered, rounded card on `--surface-1` with
19
+ // `--shadow-md`. Scales `0.96 → 1.0` on open; mirrored on close.
20
+ // * Input — single-row text field, larger than body text, no
21
+ // visible borders (clean writing-tool feel).
22
+ // * Source chips — compact filter rail mirroring Search's source
23
+ // vocabulary so quick-open can narrow to gmail / krisp / slack /
24
+ // manual / vault without invoking the full search overlay.
25
+ // * Results — vertical list, each item carries kind icon, title,
26
+ // and a faint slug subtitle. Selected item highlighted via
27
+ // `aria-selected="true"`.
28
+ //
29
+ // The component markup itself (rendered by `CommandPalette.tsx`):
30
+ //
31
+ // <dialog id="brain-cmdk-root" class="brain-cmdk-root" aria-label="Command palette">
32
+ // <div class="brain-cmdk-modal">
33
+ // <div class="brain-cmdk-chips">...</div>
34
+ // <input class="brain-cmdk-input" role="combobox" aria-expanded="false"
35
+ // aria-haspopup="listbox" aria-controls="brain-cmdk-results"
36
+ // aria-autocomplete="list" type="text" autocomplete="off"
37
+ // placeholder="Search the wiki…" aria-label="Search the wiki">
38
+ // <ul class="brain-cmdk-results" id="brain-cmdk-results" role="listbox">
39
+ // <!-- populated by commandPalette.inline.ts -->
40
+ // </ul>
41
+ // <div class="brain-cmdk-empty" hidden>No results.</div>
42
+ // <div class="brain-cmdk-footer">
43
+ // <kbd>↑</kbd><kbd>↓</kbd> navigate · <kbd>↵</kbd> open · <kbd>esc</kbd> close
44
+ // </div>
45
+ // </div>
46
+ // </dialog>
47
+ //
48
+ // brain (Lane C audit fix 2026-05-02): refactored to native
49
+ // `<dialog>`. Open/close is driven by the dialog's own `open`
50
+ // attribute (set by `dialog.showModal()`); the `.is-open` class on
51
+ // the dialog still gates the entrance/exit transitions because the
52
+ // transition properties below interpolate ON the dialog itself, and
53
+ // the class flip provides a paint-frame boundary the browser can
54
+ // animate over.
55
+ //
56
+ // Tokens consumed (from `_tokens.scss`):
57
+ // * `--surface-1`/`--surface-2` — modal + input bg
58
+ // * `--scrim` — `::backdrop` tint
59
+ // * `--ink-0`/`--ink-1`/`--ink-2` — title, subtitle, footer
60
+ // * `--rule` — input bottom hairline + footer top hairline
61
+ // * `--accent-soft`/`--accent-strong` — selected item bg + ring
62
+ // * `--shadow-md` — modal elevation
63
+ // * `--motion-fast`/`--motion-mid`/`--motion-easing` — fades + scale
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // 1. Dialog reset + positioning
67
+ // ---------------------------------------------------------------------------
68
+ //
69
+ // brain (Lane C audit fix): native `<dialog>` ships with browser
70
+ // defaults for `padding`, `border`, `background`, `position`, `width`,
71
+ // and `margin: auto` for centering. We strip them all so the
72
+ // `.brain-cmdk-modal` child is the only chrome that paints. Using
73
+ // `all: unset` would also blow away the dialog's open/closed
74
+ // semantics, so we reset properties explicitly.
75
+ //
76
+ // `position: fixed` + `inset: 0` + `display: flex` re-implements
77
+ // centering — native dialog's `margin: auto` only works when the
78
+ // dialog has an intrinsic size, which we don't want (the modal
79
+ // sizes itself).
80
+
81
+ #brain-cmdk-root.brain-cmdk-root {
82
+ // Reset native dialog chrome.
83
+ padding: 0;
84
+ border: 0;
85
+ background: transparent;
86
+ color: inherit;
87
+ // Position over the full viewport so flex centering works.
88
+ // `position: fixed` overrides the browser default of `absolute`.
89
+ position: fixed;
90
+ inset: 0;
91
+ margin: 0;
92
+ // Browser default sets `max-width` / `max-height` to `calc(100% - 6px - 2em)`;
93
+ // override so we control sizing via the modal child.
94
+ max-width: 100vw;
95
+ max-height: 100vh;
96
+ width: 100vw;
97
+ height: 100vh;
98
+ // brain: `display: none` when `[open]` is absent is the native
99
+ // default — but stock dialog also matches `display: block` on
100
+ // [open], which conflicts with our `display: flex` for centering.
101
+ // Re-pin to flex on `[open]` so the modal sits in the center.
102
+ z-index: 1000;
103
+ // brain: prevent body content showing through edges when the
104
+ // backdrop is being painted. `overflow: hidden` on the dialog
105
+ // itself isn't enough; we also clip backdrop scroll below.
106
+ }
107
+
108
+ #brain-cmdk-root.brain-cmdk-root[open] {
109
+ display: flex;
110
+ align-items: flex-start;
111
+ justify-content: center;
112
+ // 12vh top spacing so the modal sits in the upper third — standard
113
+ // PKB convention (Reflect, Notion, Linear all bias to the top).
114
+ padding-top: 12vh;
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // 2. Native `::backdrop`
119
+ // ---------------------------------------------------------------------------
120
+ //
121
+ // brain (Lane C audit fix): the browser-painted `::backdrop`
122
+ // pseudo-element replaces the manual `<div class="brain-cmdk-backdrop">`.
123
+ // The browser stacks it above all page content but below the dialog's
124
+ // own contents, no z-index gymnastics required.
125
+ //
126
+ // The opacity transition runs on the `::backdrop` directly. Stock
127
+ // browsers may not support animating the backdrop in every version;
128
+ // graceful — the worst case is the scrim appearing instantly without
129
+ // the fade, which is still a coherent UI.
130
+
131
+ #brain-cmdk-root.brain-cmdk-root::backdrop {
132
+ background: var(--scrim);
133
+ backdrop-filter: blur(8px);
134
+ -webkit-backdrop-filter: blur(8px);
135
+ opacity: 0;
136
+ transition: opacity var(--motion-mid) var(--motion-easing);
137
+ }
138
+
139
+ #brain-cmdk-root.brain-cmdk-root.is-open::backdrop {
140
+ opacity: 1;
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // 3. Modal card + source chips + input
145
+ // ---------------------------------------------------------------------------
146
+
147
+ .brain-cmdk-modal {
148
+ position: relative;
149
+ width: min(640px, calc(100vw - 2rem));
150
+ max-height: 70vh;
151
+ background: var(--surface-1);
152
+ border-radius: 12px;
153
+ box-shadow: var(--shadow-md);
154
+ display: flex;
155
+ flex-direction: column;
156
+ overflow: hidden;
157
+ // Entrance transform — small scale-up paired with the backdrop fade.
158
+ transform: scale(0.96);
159
+ opacity: 0;
160
+ transition:
161
+ transform var(--motion-mid) var(--motion-easing),
162
+ opacity var(--motion-mid) var(--motion-easing);
163
+ }
164
+
165
+ #brain-cmdk-root.brain-cmdk-root.is-open .brain-cmdk-modal {
166
+ transform: scale(1);
167
+ opacity: 1;
168
+ }
169
+
170
+ .brain-cmdk-chips {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap: 0.35rem;
174
+ padding: 0.65rem 1.2rem 0;
175
+ }
176
+
177
+ .brain-cmdk-chip {
178
+ appearance: none;
179
+ border: 1px solid var(--rule);
180
+ border-radius: 999px;
181
+ background: var(--surface-2);
182
+ color: var(--ink-2);
183
+ display: inline-flex;
184
+ align-items: center;
185
+ gap: 0.3rem;
186
+ min-height: 24px;
187
+ padding: 0.12rem 0.55rem;
188
+ cursor: pointer;
189
+ font-family: var(--bodyFont);
190
+ font-size: var(--fs-xs);
191
+ line-height: 1;
192
+ transition:
193
+ background var(--motion-fast) var(--motion-easing),
194
+ border-color var(--motion-fast) var(--motion-easing),
195
+ color var(--motion-fast) var(--motion-easing);
196
+
197
+ &[data-active="true"] {
198
+ background: var(--accent-soft);
199
+ border-color: color-mix(in srgb, var(--accent-strong) 45%, var(--rule));
200
+ color: var(--ink-0);
201
+ }
202
+
203
+ &:hover {
204
+ border-color: var(--ink-2);
205
+ color: var(--ink-0);
206
+ }
207
+ }
208
+
209
+ .brain-cmdk-chip-icon {
210
+ width: 1em;
211
+ text-align: center;
212
+ }
213
+
214
+ .brain-cmdk-chip-label {
215
+ text-transform: lowercase;
216
+ }
217
+
218
+ .brain-cmdk-input {
219
+ appearance: none;
220
+ width: 100%;
221
+ box-sizing: border-box;
222
+ padding: 1rem 1.2rem;
223
+ border: 0;
224
+ border-bottom: 1px solid var(--rule);
225
+ background: transparent;
226
+ color: var(--ink-0);
227
+ font-family: var(--bodyFont);
228
+ font-size: var(--fs-lg);
229
+ outline: none;
230
+ // brain (Linear): lining numerals + slight Geist tightness — tech
231
+ // aesthetic doesn't have old-style numerals.
232
+ font-variant-numeric: lining-nums proportional-nums;
233
+ letter-spacing: -0.011em;
234
+
235
+ &::placeholder {
236
+ color: var(--ink-2);
237
+ }
238
+ }
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // 4. Results list
242
+ // ---------------------------------------------------------------------------
243
+
244
+ .brain-cmdk-results {
245
+ list-style: none;
246
+ margin: 0;
247
+ padding: 0.4rem 0;
248
+ overflow-y: auto;
249
+ flex: 1 1 auto;
250
+ // Custom scrollbar so the list reads as part of the modal chrome.
251
+ scrollbar-width: thin;
252
+ scrollbar-color: var(--rule) transparent;
253
+
254
+ &::-webkit-scrollbar {
255
+ width: 6px;
256
+ }
257
+ &::-webkit-scrollbar-thumb {
258
+ background: var(--rule);
259
+ border-radius: 3px;
260
+ }
261
+ }
262
+
263
+ .brain-cmdk-result {
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 0.7rem;
267
+ padding: 0.55rem 1.2rem;
268
+ cursor: pointer;
269
+ color: var(--ink-1);
270
+ transition: background var(--motion-fast) var(--motion-easing);
271
+
272
+ // Kind icon (left-most). Mono-width so titles stack visually.
273
+ > .brain-cmdk-icon {
274
+ flex: 0 0 1.2rem;
275
+ text-align: center;
276
+ color: var(--ink-2);
277
+ font-size: var(--fs-sm);
278
+ }
279
+
280
+ // Body wraps title + slug subtitle.
281
+ > .brain-cmdk-body {
282
+ flex: 1 1 auto;
283
+ min-width: 0;
284
+
285
+ > .brain-cmdk-title {
286
+ color: var(--ink-0);
287
+ font-weight: 600;
288
+ font-family: var(--displayFont);
289
+ font-size: var(--fs-md);
290
+ line-height: 1.2;
291
+ // Truncate over-long titles to a single line.
292
+ white-space: nowrap;
293
+ overflow: hidden;
294
+ text-overflow: ellipsis;
295
+ }
296
+
297
+ > .brain-cmdk-slug {
298
+ color: var(--ink-2);
299
+ // brain (Linear): mono slug — Geist Mono at small size reads
300
+ // as a clean tech path, replacing the small-caps editorial
301
+ // treatment.
302
+ font-family: var(--codeFont);
303
+ font-size: var(--fs-xs);
304
+ line-height: 1.2;
305
+ white-space: nowrap;
306
+ overflow: hidden;
307
+ text-overflow: ellipsis;
308
+ letter-spacing: 0.02em;
309
+ }
310
+ }
311
+
312
+ // brain: ARIA-driven selected state. The inline script sets
313
+ // `aria-selected="true"` on the active item; styling follows the
314
+ // attribute so the visual highlight stays a11y-accessible.
315
+ &[aria-selected="true"] {
316
+ background: var(--accent-soft);
317
+ color: var(--ink-0);
318
+ > .brain-cmdk-body > .brain-cmdk-title,
319
+ > .brain-cmdk-icon {
320
+ color: var(--ink-0);
321
+ }
322
+ }
323
+
324
+ &:hover {
325
+ background: color-mix(in srgb, var(--accent-soft) 60%, transparent);
326
+ }
327
+ }
328
+
329
+ // "No results" empty state.
330
+ .brain-cmdk-empty {
331
+ padding: 2rem 1.2rem;
332
+ text-align: center;
333
+ color: var(--ink-2);
334
+ font-family: var(--bodyFont);
335
+ font-size: var(--fs-sm);
336
+ }
337
+
338
+ // ---------------------------------------------------------------------------
339
+ // 5. Footer hint row
340
+ // ---------------------------------------------------------------------------
341
+
342
+ .brain-cmdk-footer {
343
+ border-top: 1px solid var(--rule);
344
+ padding: 0.5rem 1.2rem;
345
+ display: flex;
346
+ align-items: center;
347
+ gap: 0.4rem;
348
+ color: var(--ink-2);
349
+ // brain (Linear): mono footer hint — tech aesthetic, not small-caps.
350
+ font-family: var(--codeFont);
351
+ font-size: var(--fs-xs);
352
+ letter-spacing: 0.02em;
353
+ background: var(--surface-2);
354
+
355
+ kbd {
356
+ display: inline-flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ min-width: 18px;
360
+ padding: 0.05rem 0.3rem;
361
+ background: var(--surface-1);
362
+ color: var(--ink-1);
363
+ border: 1px solid var(--rule);
364
+ border-radius: 4px;
365
+ font-family: var(--codeFont);
366
+ font-size: 0.85em;
367
+ letter-spacing: normal;
368
+ }
369
+ }
@@ -0,0 +1,228 @@
1
+ // Brain wiki — email-thread reading mode (P4.4 of the Wiki UX
2
+ // Overhaul).
3
+ //
4
+ // This file is installed at
5
+ // `<vault>/.quartz/quartz/styles/brain/_email_thread.scss` by `brain
6
+ // vault render --overlay` and is pulled into the global SCSS via
7
+ // `custom.scss`'s `@use "./brain/email_thread"` line.
8
+ //
9
+ // Scope of this file:
10
+ //
11
+ // * `<details>` + `<summary>` chrome inside `article` — paint each
12
+ // per-message collapsible as an intentional section card rather
13
+ // than the browser-default disclosure widget. Solid left rail in
14
+ // `--accent-soft`, padded body, hover state on the summary, no
15
+ // `<summary>` marker carat (replaced with our own `▸` / `▾`
16
+ // glyph that rotates on `[open]`).
17
+ // * `.brain-thread-message.brain-thread-latest` — the runtime-
18
+ // wrapped section around the leading H2 message. Mirrors the
19
+ // `<details>` body padding / left rail so the latest message
20
+ // reads as a section card too, just without the disclosure
21
+ // widget (it's always visible).
22
+ // * `.brain-email-replies-only-toggle` — the filter button the
23
+ // runtime injects at the top of `<article>`. Pill-shape, neutral
24
+ // surface, accent border on `aria-pressed="true"`.
25
+ // * `body.brain-replies-only [data-brain-is-mine="false"]` —
26
+ // visibility rule for the toggle. When the body class is on,
27
+ // hide every section whose `From:` doesn't match the user's
28
+ // email.
29
+ //
30
+ // Scope NOT covered here:
31
+ //
32
+ // * The H2 inside the latest-message section reuses
33
+ // `_article.scss`'s heading rhythm (the `> h2` rule in
34
+ // `article`). We intentionally do NOT restate it here — single
35
+ // source of truth.
36
+ // * Per-source link tinting on email-thread links lives in
37
+ // `_links.scss` (`[data-brain-source="gmail"]`).
38
+ //
39
+ // Tokens consumed (all from `_tokens.scss`):
40
+ //
41
+ // * `--accent-soft` — left rail on each message section
42
+ // * `--accent-strong` — toggle button pressed-state border
43
+ // * `--rule` — summary hover + toggle hairline
44
+ // * `--surface-1` — section + button background
45
+ // * `--ink-1` / `--ink-2` — body / dimmed text
46
+ // * `--fs-sm` / `--fs-xs` — toggle + summary type sizes
47
+ // * `--codeFont` — toggle button face (mono chrome to
48
+ // match the search-chip rail rhythm)
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // 1. Per-message <details> chrome
52
+ // ---------------------------------------------------------------------------
53
+ //
54
+ // Stock browser default for `<details>` is unstyled — a triangle
55
+ // disclosure widget on a plain text run. The brain treatment paints
56
+ // each message as a section card with a left accent rail and a
57
+ // padded body, matching the editorial rhythm in `_article.scss`. The
58
+ // `<summary>` gets a hover state + a custom rotation glyph so the
59
+ // disclosure feels intentional rather than browser-OEM.
60
+
61
+ article details.brain-thread-message,
62
+ article > details {
63
+ // brain: tag every <details> inside an article with the section
64
+ // chrome, even when the runtime hasn't yet stamped
65
+ // `.brain-thread-message`. The two selectors are equivalent in
66
+ // practice (every <details> in an article body comes from
67
+ // `to_extracted_thread`) but the comma-form keeps the rule painted
68
+ // even before the runtime fires (avoids a brief unstyled flash).
69
+ margin: 1.2rem 0;
70
+ padding: 0.6rem 0.9rem 0.6rem 1.1rem;
71
+ background: var(--surface-1, transparent);
72
+ border-left: 3px solid var(--accent-soft);
73
+ border-radius: 4px;
74
+ // brain: subtle elevation shifts on [open] — closed sections sit
75
+ // flat; opened ones lift a hair to signal "this is what you're
76
+ // reading right now."
77
+ box-shadow: 0 1px 0 var(--rule);
78
+ transition: box-shadow 120ms ease-out;
79
+
80
+ &[open] {
81
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
82
+ }
83
+
84
+ > summary {
85
+ // brain: replace the OEM triangle with our own glyph (managed
86
+ // via ::before below) so the summary reads as an intentional
87
+ // section header rather than a generic disclosure widget.
88
+ list-style: none;
89
+ cursor: pointer;
90
+ font-family: var(--codeFont);
91
+ font-size: var(--fs-sm);
92
+ color: var(--ink-2);
93
+ padding: 0.2rem 0;
94
+ user-select: none;
95
+ display: flex;
96
+ align-items: baseline;
97
+ gap: 0.4rem;
98
+ transition: color 120ms ease-out;
99
+
100
+ // Hide WebKit's native disclosure marker (Chrome / Safari).
101
+ // `list-style: none` above handles Firefox; `::-webkit-details-
102
+ // marker` covers WebKit.
103
+ &::-webkit-details-marker {
104
+ display: none;
105
+ }
106
+
107
+ // brain: custom disclosure glyph. Right-pointing triangle when
108
+ // closed, rotates 90° on `[open]`. CSS-only, no JS hook — the
109
+ // browser's native open/close still drives the state.
110
+ &::before {
111
+ content: "▸";
112
+ display: inline-block;
113
+ width: 0.8em;
114
+ color: var(--accent-strong);
115
+ transition: transform 120ms ease-out;
116
+ }
117
+
118
+ &:hover {
119
+ color: var(--ink-1);
120
+ }
121
+ }
122
+
123
+ &[open] > summary::before {
124
+ transform: rotate(90deg);
125
+ }
126
+
127
+ // brain: indent the message body inside the disclosure so the
128
+ // closed-state summary visually anchors the section, and the
129
+ // opened body reads as inset content. The inner paragraphs use
130
+ // article-default leading so they match the latest-message body.
131
+ > *:not(summary) {
132
+ margin-left: 1.2rem;
133
+ }
134
+
135
+ > p {
136
+ margin: 0.4rem 0;
137
+ color: var(--ink-1);
138
+ line-height: 1.55;
139
+ }
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // 2. Latest-message section wrapper (runtime-injected)
144
+ // ---------------------------------------------------------------------------
145
+ //
146
+ // The latest message renders as `## H2 + body` in the markdown (per
147
+ // `to_extracted_thread`'s contract — pinned by the P2.1 test). The
148
+ // runtime wraps that H2 + its trailing siblings inside a
149
+ // `<section class="brain-thread-message brain-thread-latest">` so the
150
+ // "Show only my replies" filter has a uniform DOM shape across
151
+ // latest + older messages. The styling here mirrors `<details>`'s
152
+ // chrome minus the disclosure widget (always-visible).
153
+
154
+ article section.brain-thread-message.brain-thread-latest {
155
+ display: block;
156
+ margin: 1.2rem 0 1.6rem 0;
157
+ padding: 0.4rem 0.9rem 0.6rem 1.1rem;
158
+ background: var(--surface-1, transparent);
159
+ border-left: 3px solid var(--accent-strong);
160
+ border-radius: 4px;
161
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
162
+
163
+ // The H2 inside the latest section is the most recent message's
164
+ // heading. Pull the top margin in so it lands flush with the
165
+ // section's top padding.
166
+ > h2 {
167
+ margin-top: 0;
168
+ }
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // 3. "Show only my replies" toggle button
173
+ // ---------------------------------------------------------------------------
174
+ //
175
+ // Runtime-injected at the top of <article> on email-thread pages.
176
+ // Pill chrome, mono face (matches the cmd-K palette + chip rail
177
+ // rhythm), neutral surface in default state, accent border + bold
178
+ // type on `aria-pressed="true"`.
179
+
180
+ article > .brain-email-replies-only-toggle {
181
+ display: inline-block;
182
+ margin: 0 0 1rem 0;
183
+ padding: 0.35em 0.9em;
184
+ font-family: var(--codeFont);
185
+ font-size: var(--fs-xs);
186
+ font-weight: 500;
187
+ color: var(--ink-2);
188
+ background: var(--surface-1);
189
+ border: 1px solid var(--rule);
190
+ border-radius: 999px;
191
+ cursor: pointer;
192
+ letter-spacing: 0.02em;
193
+ transition: color 120ms ease-out, border-color 120ms ease-out,
194
+ background 120ms ease-out;
195
+
196
+ &:hover {
197
+ color: var(--ink-1);
198
+ border-color: var(--accent-soft);
199
+ }
200
+
201
+ &[aria-pressed="true"] {
202
+ color: var(--accent-strong);
203
+ border-color: var(--accent-strong);
204
+ background: var(--accent-soft);
205
+ font-weight: 600;
206
+ }
207
+
208
+ &:focus-visible {
209
+ outline: 2px solid var(--accent-strong);
210
+ outline-offset: 2px;
211
+ }
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // 4. Filter visibility rules
216
+ // ---------------------------------------------------------------------------
217
+ //
218
+ // When the toggle is ON, hide every message section whose `From:`
219
+ // doesn't match the user's email. The runtime stamps
220
+ // `data-brain-is-mine="true|false"` on each section
221
+ // (`.brain-thread-message`) at boot; the body class
222
+ // `brain-replies-only` is the master switch. Using `display: none`
223
+ // (rather than `visibility` / opacity) so hidden sections also
224
+ // collapse out of the document flow — keeps the visible list compact.
225
+
226
+ body.brain-replies-only article [data-brain-is-mine="false"] {
227
+ display: none;
228
+ }