jekyll-theme-zer0 1.3.0 → 1.4.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '069f0c330f3b5a4b0fac6331452c7d9d66c5f92308ad9ffef53454e03602efd3'
4
- data.tar.gz: 15d1bdacec707645bdc406bab1229253684bf23370f949a7bb6d66b7390aa563
3
+ metadata.gz: 0ae8ec5b12734cd267a46b1b5c26ed95f7d55b004e6dd9a52111a477f6bbc395
4
+ data.tar.gz: 84ff1429cdd6504bef5977c44ff88ff9078ffdb2dcd98ea39304c10f939daceb
5
5
  SHA512:
6
- metadata.gz: 0c8788ad1dc1ba8af7ca402d5f2643eaab1c47c12307a265d427547b00678ce1115482552aa1090d6d560881282ee70eb6b2674b8bb41e03b9f4d302ce2ba47c
7
- data.tar.gz: dc0b3a38a9f1f9d7dfb272ca3c72634845c9bc2295f198b6731e43249f3ecd2a6610a1ddaf92cb20457443c71e337b4ced9352fe85d41eb641604e12d2eba426
6
+ metadata.gz: 4c194515ed9cbf3590d46b6c887b9a27c94bbdfb4a5e841417ae759e25b59bd5bf0612846ce20927361daefa42b23459152336db6fb22874ce56b05c87d0203b
7
+ data.tar.gz: d0469bd560f3ddfc264b22e0fd4b7f7ffed2b9c1a7f524f3975b5a574a7a2ae0c5c8bf53c25db53eec1bb4201e21078f95a45de46e3dd742569171ca15d29421
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.1] - 2026-04-28
4
+
5
+ ### Changed
6
+ - Version bump: patch release
7
+
8
+ ### Commits in this release
9
+ - 9830d3d refactor(obsidian): extract full graph include and sync docs (#82)
10
+
11
+
12
+ ## [1.4.0] - 2026-04-25
13
+
14
+ ### Changed
15
+ - Version bump: minor release
16
+
17
+ ### Commits in this release
18
+ - d39dfa9 Add standalone Obsidian local graph panel (#77)
19
+
20
+
3
21
  ## [1.3.0] - 2026-04-24
4
22
 
5
23
  ### Changed
@@ -39,7 +57,16 @@
39
57
 
40
58
  ## [Unreleased]
41
59
 
60
+ ### Changed
61
+ - **Docker/Jekyll build performance** — Reduced repeated full-page Liquid scans in the footer, settings offcanvas, and cookie consent includes; cached preview image checks during generation; skipped server-side Obsidian rewrites for documents without Obsidian syntax; and changed Docker dev startup to run `bundle install` only when `bundle check` reports missing dependencies. The profiled Docker build improved from 119.2s to 86.8s in local validation.
62
+
42
63
  ### Added
64
+ - **Development Automation**: Added `scripts/bin/validate` and `scripts/validate`
65
+ as the canonical preflight validation command for repository files, version
66
+ consistency, YAML/data parsing, active configuration contracts, config-file
67
+ classification, navigation data shape, Jekyll build/doctor, compiled assets,
68
+ and optional tests/Obsidian/HTMLProofer checks. CI fast checks now call
69
+ `./scripts/bin/validate --quick`.
43
70
  - **Obsidian Integration** — The repo's markdown content is now editable as an [Obsidian](https://obsidian.md) vault and rendered identically on GitHub Pages.
44
71
  - Shared vault config (`.obsidian/app.json`, `core-plugins.json`, `community-plugins.json`, `appearance.json`, `hotkeys.json`, `templates.json`) and a Templates-compatible note template at `pages/_notes/_templates/note-template.md`.
45
72
  - Liquid-generated `assets/data/wiki-index.json` listing every collection document and standalone page (title, basename, permalink, tags, aliases, excerpt) — works on the default GitHub Pages remote_theme build, no plugin whitelist changes required.
@@ -76,6 +103,20 @@
76
103
  `Your Site Title`, `My Awesome Site`, `Welcome`, `Untitled`, or empty).
77
104
 
78
105
  ### Fixed
106
+ - **Obsidian Local Graph**: Moved the local graph out of the documentation
107
+ navigation sidebar into its own collapsible side panel with a larger canvas
108
+ and resize-on-open behavior so Cytoscape renders cleanly. Pages with no
109
+ local wiki-link neighbors now keep the graph control visible and render a
110
+ current-page-only graph instead of hiding the panel.
111
+ - **Obsidian Resolver**: The client-side wiki-link resolver now receives
112
+ baseurl-safe index, attachment, and tag URLs from Liquid and derives a safe
113
+ fallback from its script path for GitHub Pages project sites.
114
+ - **Backlinks**: The linked-mentions include now skips draft and unpublished
115
+ candidates unless `site.show_drafts` is enabled.
116
+ - **Validation**: `scripts/bin/validate --quick` now accepts YAML anchors and
117
+ date values used by repository config/data files.
118
+ - **Tests**: `test/test_runner.sh` now includes an `obsidian` suite key/name so
119
+ suite keys, scripts, and labels stay aligned.
79
120
  - **Footer Quick Links no longer 404 on bare-minimum sites.**
80
121
  `_includes/core/footer.html` previously hard-coded links to
81
122
  `/about/`, `/services/`, `/news/`, `/contact/`, `/privacy-policy`, and
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  title: zer0-mistakes
3
3
  sub-title: AI-Native Jekyll Theme
4
4
  description: AI-native Jekyll theme for GitHub Pages — Docker-first development, AI-powered installation, multi-agent integration (Copilot, Codex, Cursor, Claude), AI preview-image generation, and AIEO content optimization with Bootstrap 5.3.
5
- version: 1.3.0
5
+ version: 1.4.1
6
6
  layout: landing
7
7
  tags:
8
8
  - jekyll
@@ -20,7 +20,7 @@ categories:
20
20
  - bootstrap
21
21
  - ai-tooling
22
22
  created: 2024-02-10T23:51:11.480Z
23
- lastmod: 2026-04-24T23:27:06.000Z
23
+ lastmod: 2026-04-28T21:04:09.000Z
24
24
  draft: false
25
25
  permalink: /
26
26
  slug: zer0
@@ -1138,7 +1138,7 @@ git push origin feature/awesome-feature
1138
1138
 
1139
1139
  | Metric | Value |
1140
1140
  |--------|-------|
1141
- | **Current Version** | 1.3.0 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
1141
+ | **Current Version** | 1.4.1 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
1142
1142
  | **Documented Features** | 43 ([Feature Registry](https://github.com/bamr87/zer0-mistakes/blob/main/_data/features.yml)) |
1143
1143
  | **Setup Time** | 2-5 minutes ([install.sh benchmarks](https://github.com/bamr87/zer0-mistakes/blob/main/install.sh)) |
1144
1144
  | **Documentation Pages** | 70+ ([browse docs](/pages/)) |
@@ -1189,6 +1189,6 @@ And these AI partners that make zer0-mistakes truly AI-native:
1189
1189
 
1190
1190
  **Built with ❤️ — and a little help from our AI partners — for the Jekyll community**
1191
1191
 
1192
- **v1.3.0** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
1192
+ **v1.4.1** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
1193
1193
 
1194
1194
 
@@ -59,9 +59,8 @@ Configuration: Uses site.posthog settings from _config.yml
59
59
  </p>
60
60
  <p class="mb-2 mb-lg-0 small text-white-50">
61
61
  This website uses cookies and similar technologies to enhance your browsing experience, analyze traffic, and provide personalized content.
62
- {% assign _pp = site.html_pages | where: "url", "/privacy-policy/" | first %}
63
- {% unless _pp %}{% assign _pp = site.html_pages | where: "url", "/privacy-policy" | first %}{% endunless %}
64
- {% if _pp or site.privacy_policy_url %}
62
+ {% assign cookie_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
63
+ {% if site.privacy_policy_url or cookie_page_urls contains "|/privacy-policy/|" or cookie_page_urls contains "|/privacy-policy|" %}
65
64
  <a href="{{ site.privacy_policy_url | default: '/privacy-policy/' | relative_url }}" class="text-white text-decoration-underline">Learn more in our Privacy Policy</a>.
66
65
  {% endif %}
67
66
  </p>
@@ -22,6 +22,7 @@
22
22
  -->
23
23
 
24
24
  {% include components/env-detect.html %}
25
+ {% assign info_section_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
25
26
  <!-- Settings Offcanvas -->
26
27
  <div class="offcanvas offcanvas-end" tabindex="-1" id="info-section" aria-labelledby="infoSectionLabel">
27
28
 
@@ -111,9 +112,12 @@
111
112
  <!-- Admin Quick Links — only render links to pages that actually exist
112
113
  in the build (prevents 404s on bare-minimum / remote-theme sites
113
114
  that haven't created the admin pages). -->
114
- {% assign _cfg_page = site.html_pages | where: "url", "/about/config/" | first %}
115
- {% assign _theme_page = site.html_pages | where: "url", "/about/settings/theme/" | first %}
116
- {% assign _nav_page = site.html_pages | where: "url", "/about/settings/navigation/" | first %}
115
+ {% assign _cfg_page = false %}
116
+ {% assign _theme_page = false %}
117
+ {% assign _nav_page = false %}
118
+ {% if info_section_page_urls contains "|/about/config/|" %}{% assign _cfg_page = true %}{% endif %}
119
+ {% if info_section_page_urls contains "|/about/settings/theme/|" %}{% assign _theme_page = true %}{% endif %}
120
+ {% if info_section_page_urls contains "|/about/settings/navigation/|" %}{% assign _nav_page = true %}{% endif %}
117
121
  {% if _cfg_page or _theme_page or _nav_page %}
118
122
  <div class="mb-3">
119
123
  <h6 class="text-body-secondary small text-uppercase fw-semibold mb-2">
@@ -145,7 +149,8 @@
145
149
  {% include components/env-switcher.html %}
146
150
 
147
151
  <!-- Admin Link — only when target page exists -->
148
- {% assign _env_page = site.html_pages | where: "url", "/about/settings/environment/" | first %}
152
+ {% assign _env_page = false %}
153
+ {% if info_section_page_urls contains "|/about/settings/environment/|" %}{% assign _env_page = true %}{% endif %}
149
154
  {% if _env_page %}
150
155
  <div class="mt-4 pt-3 border-top">
151
156
  <a href="{{ '/about/settings/environment/' | relative_url }}" class="btn btn-outline-secondary btn-sm w-100">
@@ -35,4 +35,11 @@
35
35
 
36
36
  <!-- Obsidian wiki-link / embed / tag client-side resolver (fallback for the
37
37
  remote_theme GH Pages build, where _plugins/obsidian_links.rb cannot run). -->
38
- <script defer src="{{ '/assets/js/obsidian-wiki-links.js' | relative_url }}"></script>
38
+ {%- assign obsidian_attachments_path = site.obsidian.attachments_path | default: '/assets/images/notes' -%}
39
+ <script>
40
+ window.OBSIDIAN_CONFIG = window.OBSIDIAN_CONFIG || {};
41
+ window.OBSIDIAN_CONFIG.wikiIndexUrl = {{ '/assets/data/wiki-index.json' | relative_url | jsonify }};
42
+ window.OBSIDIAN_CONFIG.attachmentsPath = {{ obsidian_attachments_path | relative_url | jsonify }};
43
+ window.OBSIDIAN_CONFIG.tagBase = {{ '/tags/' | relative_url | jsonify }};
44
+ </script>
45
+ <script defer src="{{ '/assets/js/obsidian-wiki-links.js' | relative_url }}"></script>
@@ -41,6 +41,10 @@
41
41
  {%- assign _backlinks = "" | split: "" -%}
42
42
  {%- for _doc in _candidates -%}
43
43
  {%- if _doc.url == _self_url -%}{%- continue -%}{%- endif -%}
44
+ {%- comment -%} Hide draft/unpublished references unless this build includes drafts. {%- endcomment -%}
45
+ {%- unless site.show_drafts == true -%}
46
+ {%- if _doc.published == false or _doc.draft == true -%}{%- continue -%}{%- endif -%}
47
+ {%- endunless -%}
44
48
  {%- assign _body = _doc.content | default: "" -%}
45
49
  {%- assign _body_lower = _body | downcase -%}
46
50
  {%- assign _matched = false -%}
@@ -31,6 +31,7 @@
31
31
  -->
32
32
 
33
33
  <footer class="bd-footer border-top" role="contentinfo">
34
+ {% assign footer_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
34
35
  <!-- Powered by Row -->
35
36
  <div class="container-xl my-3">
36
37
  <ul class="nav justify-content-end list-unstyled d-flex align-items-center flex-wrap" aria-label="Powered by technologies">
@@ -100,8 +101,8 @@
100
101
  {% assign _parts = entry | split: "," %}
101
102
  {% assign _label = _parts[0] %}
102
103
  {% assign _url = _parts[1] %}
103
- {% assign _page = site.html_pages | where: "url", _url | first %}
104
- {% if _page %}
104
+ {% assign _needle = "|" | append: _url | append: "|" %}
105
+ {% if footer_page_urls contains _needle %}
105
106
  <li><a href="{{ _url | relative_url }}" class="text-light text-decoration-none">{{ _label }}</a></li>
106
107
  {% endif %}
107
108
  {% endfor %}
@@ -169,14 +170,14 @@
169
170
  <div class="col-md-4 text-md-end">
170
171
  <ul class="list-inline mb-0">
171
172
  {% comment %} Only render policy links whose pages exist (or that are explicitly configured). {% endcomment %}
172
- {% assign _privacy = site.html_pages | where: "url", "/privacy-policy/" | first %}
173
- {% unless _privacy %}{% assign _privacy = site.html_pages | where: "url", "/privacy-policy" | first %}{% endunless %}
174
- {% if _privacy or site.privacy_policy_url %}
173
+ {% assign _privacy_needle = "|/privacy-policy/|" %}
174
+ {% assign _privacy_alt_needle = "|/privacy-policy|" %}
175
+ {% if site.privacy_policy_url or footer_page_urls contains _privacy_needle or footer_page_urls contains _privacy_alt_needle %}
175
176
  <li class="list-inline-item"><a href="{{ site.privacy_policy_url | default: '/privacy-policy' | relative_url }}" class="text-light text-decoration-none">Privacy Policy</a></li>
176
177
  {% endif %}
177
- {% assign _terms = site.html_pages | where: "url", "/terms-of-service/" | first %}
178
- {% unless _terms %}{% assign _terms = site.html_pages | where: "url", "/terms-of-service" | first %}{% endunless %}
179
- {% if _terms or site.terms_of_service_url %}
178
+ {% assign _terms_needle = "|/terms-of-service/|" %}
179
+ {% assign _terms_alt_needle = "|/terms-of-service|" %}
180
+ {% if site.terms_of_service_url or footer_page_urls contains _terms_needle or footer_page_urls contains _terms_alt_needle %}
180
181
  <li class="list-inline-item"><a href="{{ site.terms_of_service_url | default: '/terms-of-service' | relative_url }}" class="text-light text-decoration-none">Terms of Service</a></li>
181
182
  {% endif %}
182
183
  <li class="list-inline-item">
@@ -1,21 +1,21 @@
1
1
  {%- comment -%}
2
2
  ===================================================================
3
- LOCAL GRAPH WIDGETsidebar mini graph
3
+ LOCAL GRAPH SIDEBARcollapsible page-scoped graph
4
4
  ===================================================================
5
5
 
6
6
  File: local-graph.html
7
7
  Path: _includes/navigation/local-graph.html
8
- Purpose: Renders a small "local graph" of the current page and its
9
- immediate wiki-link neighbors in the left sidebar (Obsidian-
10
- style local graph view).
8
+ Purpose: Renders a collapsible, page-scoped graph of the current page and
9
+ its immediate wiki-link neighbors in a separate side panel.
11
10
 
12
11
  Behavior:
13
12
  - Reads /assets/data/wiki-index.json
14
13
  - Finds the current page by window.location.pathname (with title/basename
15
14
  fallback for permalink quirks)
16
15
  - BFS depth=1 by default (current + direct neighbors, in & out)
17
- - Hides itself if the current page is not in the wiki-index, has no
18
- neighbors, or the index can't be loaded
16
+ - Renders the current page as a single-node graph when it has no neighbors
17
+ - Hides itself if the current page is not in the wiki-index or the index
18
+ can't be loaded
19
19
  - Lazily loads cytoscape.js from CDN with SRI + crossorigin
20
20
 
21
21
  Opt-out: set `local_graph: false` in page front matter.
@@ -25,20 +25,55 @@
25
25
  {%- endcomment -%}
26
26
  {%- if page.local_graph != false -%}
27
27
  {%- assign lg_depth = page.local_graph_depth | default: 1 -%}
28
- <div class="obsidian-local-graph-widget mb-3" aria-label="Local graph">
29
- <div class="d-flex align-items-center justify-content-between mb-1">
30
- <span class="text-uppercase fw-semibold small text-secondary">
31
- <i class="bi bi-diagram-3 me-1" aria-hidden="true"></i>Local graph
32
- </span>
33
- <a href="{{ '/docs/obsidian/graph/' | relative_url }}"
34
- class="small text-decoration-none"
35
- title="Open the full site graph">full&nbsp;&rsaquo;</a>
36
- </div>
37
- <div id="obsidian-local-graph"
38
- data-depth="{{ lg_depth }}"
39
- data-index-url="{{ '/assets/data/wiki-index.json' | relative_url }}"
40
- role="img"
41
- aria-label="Local graph of pages linked to this page"></div>
28
+ <div class="obsidian-local-graph-fab d-print-none" data-obsidian-local-graph-toggle>
29
+ <button class="btn btn-primary rounded-circle shadow-lg obsidian-local-graph-toggle"
30
+ type="button"
31
+ data-bs-toggle="offcanvas"
32
+ data-bs-target="#obsidianLocalGraphPanel"
33
+ aria-controls="obsidianLocalGraphPanel"
34
+ aria-label="Open local graph"
35
+ title="Open local graph">
36
+ <i class="bi bi-diagram-3 fs-5" aria-hidden="true"></i>
37
+ </button>
42
38
  </div>
39
+
40
+ <aside class="offcanvas offcanvas-start obsidian-local-graph-panel"
41
+ tabindex="-1"
42
+ id="obsidianLocalGraphPanel"
43
+ aria-labelledby="obsidianLocalGraphLabel"
44
+ data-bs-scroll="true"
45
+ data-bs-backdrop="false"
46
+ data-obsidian-local-graph-panel>
47
+ <div class="offcanvas-header">
48
+ <h2 class="offcanvas-title h5 mb-0" id="obsidianLocalGraphLabel">
49
+ <i class="bi bi-diagram-3 me-2" aria-hidden="true"></i>Local graph
50
+ </h2>
51
+ <button type="button"
52
+ class="btn-close"
53
+ data-bs-dismiss="offcanvas"
54
+ data-bs-target="#obsidianLocalGraphPanel"
55
+ aria-label="Close local graph"></button>
56
+ </div>
57
+ <div class="offcanvas-body">
58
+ <div class="obsidian-local-graph-widget" aria-label="Local graph">
59
+ <div class="obsidian-local-graph-meta d-flex align-items-center justify-content-between gap-2 mb-3">
60
+ <span class="badge text-bg-secondary">Depth {{ lg_depth }}</span>
61
+ <a href="{{ '/docs/obsidian/graph/' | relative_url }}"
62
+ class="btn btn-outline-primary btn-sm"
63
+ title="Open the full site graph">
64
+ <i class="bi bi-arrows-fullscreen me-1" aria-hidden="true"></i>Full graph
65
+ </a>
66
+ </div>
67
+ <div id="obsidian-local-graph"
68
+ data-depth="{{ lg_depth }}"
69
+ data-index-url="{{ '/assets/data/wiki-index.json' | relative_url }}"
70
+ role="img"
71
+ aria-label="Local graph of pages linked to this page"></div>
72
+ <p class="obsidian-local-graph-status small text-secondary mt-2 mb-0"
73
+ data-obsidian-local-graph-status
74
+ role="status">Loading graph...</p>
75
+ </div>
76
+ </div>
77
+ </aside>
43
78
  <script src="{{ '/assets/js/obsidian-local-graph.js' | relative_url }}" defer></script>
44
79
  {%- endif -%}
@@ -48,12 +48,6 @@
48
48
  <!-- Scrollable content area with dynamic navigation options -->
49
49
  <div class="offcanvas-body overflow-auto">
50
50
 
51
- <!-- ========================== -->
52
- <!-- LOCAL GRAPH WIDGET -->
53
- <!-- ========================== -->
54
- <!-- Mini Obsidian-style local graph of the current page + neighbors -->
55
- {% include navigation/local-graph.html %}
56
-
57
51
  <!-- ========================== -->
58
52
  <!-- AUTO MODE: Collection Docs -->
59
53
  <!-- ========================== -->
@@ -0,0 +1,170 @@
1
+ <style>
2
+ /* Scoped to the graph page so we don't leak into other docs. */
3
+ #obsidian-graph {
4
+ width: 100%;
5
+ height: 82vh;
6
+ min-height: 620px;
7
+ border: 1px solid var(--bs-border-color, #dee2e6);
8
+ border-radius: var(--bs-border-radius-lg, .5rem);
9
+ background: var(--bs-tertiary-bg, #f8f9fa);
10
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 4px 12px rgba(0, 0, 0, .04);
11
+ position: relative;
12
+ overflow: hidden;
13
+ }
14
+ #obsidian-graph-stats {
15
+ display: flex;
16
+ flex-wrap: wrap;
17
+ gap: .375rem;
18
+ }
19
+ #obsidian-graph-stats .badge {
20
+ font-size: .8125rem;
21
+ font-weight: 500;
22
+ padding: .35rem .6rem;
23
+ }
24
+ .obsidian-graph-toolbar {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ gap: .5rem .75rem;
28
+ align-items: center;
29
+ margin: .75rem 0;
30
+ padding: .625rem .75rem;
31
+ background: var(--bs-tertiary-bg, #f8f9fa);
32
+ border: 1px solid var(--bs-border-color, #dee2e6);
33
+ border-radius: var(--bs-border-radius, .375rem);
34
+ }
35
+ .obsidian-graph-toolbar .form-control {
36
+ max-width: 280px;
37
+ flex: 1 1 200px;
38
+ }
39
+ .obsidian-graph-toolbar .form-check {
40
+ margin-bottom: 0;
41
+ }
42
+ #obsidian-graph-status {
43
+ flex-basis: 100%;
44
+ font-size: .8125rem;
45
+ color: var(--bs-secondary-color, #6c757d);
46
+ min-height: 1.25rem;
47
+ }
48
+ .obsidian-graph-legend {
49
+ display: flex;
50
+ flex-wrap: wrap;
51
+ gap: .5rem;
52
+ margin-top: .75rem;
53
+ font-size: .8125rem;
54
+ }
55
+ .obsidian-graph-legend > span {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: .375rem;
59
+ padding: .25rem .55rem;
60
+ background: var(--bs-body-bg, #fff);
61
+ border: 1px solid var(--bs-border-color, #dee2e6);
62
+ border-radius: 999px;
63
+ color: var(--bs-body-color, #212529);
64
+ }
65
+ .obsidian-graph-legend .swatch {
66
+ display: inline-block;
67
+ width: .7rem;
68
+ height: .7rem;
69
+ border-radius: 50%;
70
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, .15);
71
+ }
72
+ .obsidian-graph-legend .swatch-broken {
73
+ border: 1.5px dashed #dc3545;
74
+ background: transparent !important;
75
+ box-shadow: none;
76
+ }
77
+ .obsidian-graph-tips {
78
+ margin-top: .75rem;
79
+ padding: .625rem .75rem;
80
+ font-size: .8125rem;
81
+ color: var(--bs-secondary-color, #6c757d);
82
+ background: var(--bs-tertiary-bg, #f8f9fa);
83
+ border-left: 3px solid var(--bs-primary, #0d6efd);
84
+ border-radius: .25rem;
85
+ }
86
+ .obsidian-graph-tips strong {
87
+ color: var(--bs-body-color, #212529);
88
+ }
89
+ </style>
90
+
91
+ # Obsidian Graph View
92
+
93
+ A live, force-directed map of every page on this site and every
94
+ `[[wiki-link]]` between them. This is the rendered-site equivalent of
95
+ Obsidian's local graph view — built from the same
96
+ [`assets/data/wiki-index.json`]({{ "/assets/data/wiki-index.json" | relative_url }})
97
+ that powers the [client-side resolver]({{ "/docs/obsidian/syntax-reference/" | relative_url }})
98
+ and [backlinks panel]({{ "/docs/obsidian/syntax-reference/#backlinks-panel" | relative_url }}).
99
+
100
+ <div id="obsidian-graph-stats" class="mb-2" aria-live="polite"></div>
101
+
102
+ <div class="obsidian-graph-toolbar">
103
+ <input type="search"
104
+ class="form-control form-control-sm"
105
+ id="obsidian-graph-search"
106
+ placeholder="Filter nodes by title…"
107
+ aria-label="Filter graph nodes by title" />
108
+ <button type="button"
109
+ class="btn btn-outline-secondary btn-sm"
110
+ id="obsidian-graph-fit">
111
+ <i class="bi bi-arrows-fullscreen" aria-hidden="true"></i>
112
+ Reset view
113
+ </button>
114
+ <div class="form-check form-switch">
115
+ <input class="form-check-input"
116
+ type="checkbox"
117
+ role="switch"
118
+ id="obsidian-graph-orphans" />
119
+ <label class="form-check-label" for="obsidian-graph-orphans">
120
+ Show orphans
121
+ </label>
122
+ </div>
123
+ <span id="obsidian-graph-status" role="status"></span>
124
+ </div>
125
+
126
+ <div id="obsidian-graph" role="img" aria-label="Site knowledge graph"></div>
127
+
128
+ <div class="obsidian-graph-legend" aria-label="Graph legend">
129
+ <span><span class="swatch" style="background:#0d6efd"></span>Posts</span>
130
+ <span><span class="swatch" style="background:#198754"></span>Docs</span>
131
+ <span><span class="swatch" style="background:#6f42c1"></span>Notes</span>
132
+ <span><span class="swatch" style="background:#d63384"></span>Notebooks</span>
133
+ <span><span class="swatch" style="background:#fd7e14"></span>Quickstart</span>
134
+ <span><span class="swatch" style="background:#6c757d"></span>Pages</span>
135
+ <span><span class="swatch swatch-broken"></span>Broken links</span>
136
+ </div>
137
+
138
+ <div class="obsidian-graph-tips">
139
+ <strong>Tips:</strong> click a node to open the page · ⌘/Ctrl-click to
140
+ open in a new tab · drag to reposition · scroll to zoom · hover to
141
+ highlight a node's neighborhood · type in the search box to filter.
142
+ </div>
143
+
144
+ ## How it's built
145
+
146
+ | Piece | File |
147
+ | --- | --- |
148
+ | Build-time index (nodes + outgoing edges) | [`assets/data/wiki-index.json`]({{ "/assets/data/wiki-index.json" | relative_url }}) (Liquid template at [`assets/data/wiki-index.json`](https://github.com/bamr87/zer0-mistakes/blob/main/assets/data/wiki-index.json)) |
149
+ | Renderer | `assets/js/obsidian-graph.js` |
150
+ | Layout engine | [cytoscape.js](https://js.cytoscape.org/) (loaded from CDN, only on this page) |
151
+
152
+ Outgoing edges come from the same `[[…]]` syntax the resolver handles —
153
+ unresolved targets show up as dashed red nodes so you can find dangling
154
+ links at a glance. The graph is regenerated every Jekyll build; nothing
155
+ runs client-side except cytoscape's force layout.
156
+
157
+ <!-- Cytoscape.js (only loaded on this page). -->
158
+ <script src="https://cdn.jsdelivr.net/npm/cytoscape@3.30.0/dist/cytoscape.min.js"
159
+ integrity="sha384-kpMsYllYzyaWU69Piok08rPNktpnjqAoDMdB00fjqUkEk3lkuUbSuwJ+oXrjvN6B"
160
+ crossorigin="anonymous"
161
+ defer></script>
162
+ <script src="{{ '/assets/js/obsidian-graph.js' | relative_url }}" defer></script>
163
+
164
+ ## See also
165
+
166
+ - [[Obsidian Vault Integration]]
167
+ - [[Obsidian Syntax Reference]]
168
+ - [[Obsidian Authoring Workflow]]
169
+ - [[Getting Started with the Obsidian Vault]]
170
+ - [[Obsidian Integration Troubleshooting]]