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