jekyll-client-search 0.1.0

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +121 -0
  3. data/LICENSE +22 -0
  4. data/NOTICE +49 -0
  5. data/README.developer.md +204 -0
  6. data/README.md +948 -0
  7. data/assets/adapters/elasticlunr.js +59 -0
  8. data/assets/adapters/minisearch.js +57 -0
  9. data/assets/adapters/semantic.js +154 -0
  10. data/assets/client-search-base.js +294 -0
  11. data/assets/client-search-related.js +176 -0
  12. data/assets/includes/related-articles.html +36 -0
  13. data/assets/layouts/post-with-related.html +54 -0
  14. data/assets/query-embedders/ollama-api.js +63 -0
  15. data/assets/query-embedders/transformers-worker.js +130 -0
  16. data/assets/query-embedders/transformers.js +223 -0
  17. data/docs/assets/icon-256.png +0 -0
  18. data/docs/assets/icon.svg +133 -0
  19. data/lib/jekyll/client_search/configuration.rb +152 -0
  20. data/lib/jekyll/client_search/configuration_accessors.rb +48 -0
  21. data/lib/jekyll/client_search/document_builder.rb +66 -0
  22. data/lib/jekyll/client_search/embedder_config_page.rb +14 -0
  23. data/lib/jekyll/client_search/embedding_configuration.rb +95 -0
  24. data/lib/jekyll/client_search/generator.rb +134 -0
  25. data/lib/jekyll/client_search/index_cache.rb +82 -0
  26. data/lib/jekyll/client_search/live_search_configuration.rb +70 -0
  27. data/lib/jekyll/client_search/ollama_embedding_adapter.rb +59 -0
  28. data/lib/jekyll/client_search/query_embedder_configuration.rb +127 -0
  29. data/lib/jekyll/client_search/related_analyzer.rb +152 -0
  30. data/lib/jekyll/client_search/related_configuration.rb +103 -0
  31. data/lib/jekyll/client_search/related_page.rb +14 -0
  32. data/lib/jekyll/client_search/related_tag.rb +76 -0
  33. data/lib/jekyll/client_search/runtime_config_page.rb +30 -0
  34. data/lib/jekyll/client_search/search_index_page.rb +15 -0
  35. data/lib/jekyll/client_search/search_tag.rb +100 -0
  36. data/lib/jekyll/client_search/tasks.rb +137 -0
  37. data/lib/jekyll/client_search/version.rb +7 -0
  38. data/lib/jekyll/client_search.rb +25 -0
  39. data/lib/jekyll-client-search.rb +3 -0
  40. metadata +104 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b07a5519cf45012ce0d198cedfe93e1e93e939ed9d9221386af87c10854f0cf4
4
+ data.tar.gz: 630a3dd0f345e4e263c8228ac3a565610b435723860dac1125e86bf780d072b1
5
+ SHA512:
6
+ metadata.gz: 0d53f70fa2e1f37facfebe56ebf3384f3d5c376672e08f93cc5a3a761b03a294843b0cf236cbda9ffa7a7ace87261683a525f3a5e12e9da61592946cc878024a
7
+ data.tar.gz: 458dc19c9c0ad2b6c017057694b1af2103d918f16584205ec7c144a5e696fcb0d5609508fa414a7cb91facccf39a75dcd0c1c3e8c8a8e67bac8d1a09665419b5
data/CHANGELOG.md ADDED
@@ -0,0 +1,121 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-08-25
4
+
5
+ - Add a pluggable search engine architecture with a base runtime and adapters.
6
+ - Support `minisearch`, `elasticlunr`, and `semantic` engines via `engine` config.
7
+ - Split the browser runtime into `client-search-base.js` (engine-agnostic shell
8
+ that owns the two-stage search strategy) and per-engine adapters under
9
+ `assets/adapters/` that act as pure translators.
10
+ - Add ElasticLunr adapter that translates the uniform query model into
11
+ ElasticLunr's native API.
12
+ - Add semantic adapter that ranks documents by cosine similarity against
13
+ pre-computed embeddings, supports async/typed-array query vectors, caches
14
+ query embeddings, and ignores stale asynchronous results.
15
+ - Add packaged query embedders for semantic search: transformers.js running
16
+ entirely in the browser (default) and an Ollama-compatible remote HTTP API.
17
+ Generate browser embedder configuration from `_config.yml`, support fully
18
+ self-hosted library/model/WASM assets, and apply model-specific asymmetric
19
+ document/query prefixes for EmbeddingGemma and Nomic.
20
+ - Pin transformers.js, run browser model inference in a Web Worker, add
21
+ loading progress, retry/timeout controls, query truncation, strict finite
22
+ vector/dimension validation, and main-thread fallback.
23
+ - Add optional engine-aware live search with configurable minimum length,
24
+ lexical/semantic debounce, URL synchronization, stale-result protection,
25
+ and cancellation of obsolete remote API requests.
26
+ - Add optional build-time related-article analysis combining shared tags,
27
+ exact/parent categories, and vector similarity above a configurable cutoff.
28
+ Write relations to a separate JSON artifact and provide a lightweight
29
+ browser renderer with relevance/newest sorting; search results can also be
30
+ sorted by relevance or publication date, and lexical indexes can omit
31
+ temporary analysis embeddings.
32
+ - Add `{% related_articles %}` Liquid tag for one-line adoption in any post
33
+ layout. Supports `sort:date` and `no_scripts` parameters; renders nothing
34
+ when related is disabled.
35
+ - Add `{% search_form %}` Liquid tag for config-driven search form + scripts.
36
+ Outputs the form HTML, status/results containers, and all runtime scripts
37
+ (engine library, runtime config, embedder config, query embedder, base
38
+ runtime, adapter) in the correct order based on `_config.yml`. Supports
39
+ `scripts_only` and `no_scripts` modes. Changing engines requires zero
40
+ template changes.
41
+ - Enable live search by default for lexical engines (MiniSearch, ElasticLunr)
42
+ and disable by default for semantic (to avoid embedding every keystroke).
43
+ Form submission always remains available. Set `live_search.enabled: false`
44
+ to restore submit-only behavior.
45
+ - Add `engine_url`, `engine_sri`, and `engine_crossorigin` config options
46
+ with per-engine CDN defaults (MiniSearch 7.2.0, ElasticLunr 0.9.5).
47
+ Override to self-host or pin a version; set `engine_sri` for Subresource
48
+ Integrity.
49
+ - Add rake tasks `jekyll_client_search:reference_files` (list, diff, and
50
+ status of reference files) and `jekyll_client_search:install` (copy
51
+ reference layouts/includes into the consuming site, with overwrite
52
+ protection for modified copies).
53
+ - Ship reference `_includes/related-articles.html` and
54
+ `_layouts/post-with-related.html` for copy-paste adoption.
55
+ - Extend `client-search-related.js` with `renderItem` callback for custom
56
+ list item rendering, `filter` callback for narrowing visible relations,
57
+ richer default rendering (date, shared tags, shared categories, excerpt),
58
+ and `data-related-sort` attribute support for the Liquid tag.
59
+ - Add fixture site demo page (`spec/fixtures/site/related-test.html`) and
60
+ post layout (`spec/fixtures/site/_layouts/post.html`) exercising all
61
+ related-articles adoption paths. Add system tests verifying relations file
62
+ structure, runtime asset copying, Liquid tag rendering, demo page content,
63
+ self-exclusion, and score sorting.
64
+ - Add embedding generation at build time via a local Ollama server using
65
+ the `ollama-ruby` gem (optional dependency, lazy-loaded).
66
+ - Add incremental indexing via an atomically written content-hash cache
67
+ (`.jekyll-client-search-cache.json`) — unchanged documents reuse cached
68
+ embeddings only when the provider, model, endpoint, and schema match.
69
+ - Add configurable Ollama connection/read timeouts and fail semantic builds by
70
+ default when an embedding cannot be generated.
71
+ - Report browser query-embedding failures as unavailable instead of presenting
72
+ them as valid zero-result searches.
73
+ - Load only `cgi/escape` for Ruby 3.5 compatibility.
74
+ - Add uniform JS test suite parameterized over all adapters — the same
75
+ assertions run against each engine.
76
+ - Add Ruby system tests that build an 80-post fixture of 40 Wikipedia articles
77
+ and 40 unique arXiv papers per engine, plus JS system tests for baseline
78
+ results (AND, OR fallback, cross-domain) in jsdom.
79
+ - Add Ollama integration tests that generate real embeddings via a local
80
+ Ollama server with `embeddinggemma:300m` and verify cache reuse.
81
+ - Add RuboCop, ESLint, SimpleCov thresholds, and Ruby/JavaScript dependency
82
+ audits to CI.
83
+ - Add reproducible performance history for cold, warm, incremental, index-size,
84
+ query-embedding, and engine-core search latency measurements.
85
+ - Correct fixture documentation and make the Wikipedia/arXiv regeneration
86
+ scripts remove dated outputs correctly; arXiv regeneration now deduplicates
87
+ paper IDs across category queries and records the actual per-paper license.
88
+ - Add `wikipedia_oldid` and permanent revision links to all Wikipedia fixture
89
+ posts so the exact downloaded version can be referenced as articles change.
90
+ - Remove arXiv fixture posts from the repository due to mixed/restrictive
91
+ licenses (some papers use CC BY-NC, CC BY-NC-ND, or arXiv non-exclusive
92
+ licenses that do not clearly permit redistribution). Developers run
93
+ `download_arxiv.rb` locally to fetch them. The committed baseline JSON
94
+ and semantic embeddings remain as stable test artifacts. See
95
+ README.developer.md and NOTICE for details.
96
+ - Add README.developer.md with fixture setup and LLM/vector testing
97
+ instructions, and a NOTICE file with source attribution.
98
+ - Make JS system, gold, comparison, and meta tests consume required committed
99
+ baseline fixtures directly, eliminating order-dependent test setup and skips.
100
+ - Preserve `#` as the safe link fallback for incomplete index records.
101
+ - Document `jekyll-pagefind` as a recommended alternative for users who
102
+ want Pagefind's HTML-crawling indexing approach.
103
+ - Add a safe Jekyll generator for MiniSearch JSON indexes.
104
+ - Support posts and configurable custom collections.
105
+ - Optionally include titled Jekyll pages.
106
+ - Include searchable title, excerpt, content, categories, and tags fields.
107
+ - Copy the browser runtime asset into the consuming site's assets directory.
108
+ - Add RSpec coverage for configuration and document normalization.
109
+ - Add Gundestrup.dk as the local integration test platform.
110
+ - Validate configuration and reject unsafe output paths.
111
+ - Deduplicate indexed documents and normalize searchable metadata.
112
+ - Harden browser result rendering with DOM APIs and same-origin URLs.
113
+ - Add runtime asset, generated page, page indexing, disabled mode, and edge-case tests.
114
+ - Add browser runtime behavior and URL-safety tests with Node.js and jsdom.
115
+ - Standardize development on Ruby 3.4.10 and test supported Ruby 3.2, 3.3,
116
+ and 3.4 versions in CI with Bundler 4.0.9.
117
+ - License the gem under AGPL-3.0-or-later.
118
+ - Rename gem from `jekyll-elasticlunr-search` to `jekyll-client-search`.
119
+ - Replace Elasticlunr.js browser runtime with MiniSearch 7.2.0.
120
+ - Implement two-stage search: exact AND with prefix, then fuzzy OR fallback.
121
+ - Change configuration key from `elasticlunr_search` to `client_search`.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2026 Svend Gundestrup
5
+
6
+ This project is free software: you can redistribute it and/or modify it under
7
+ the terms of the GNU Affero General Public License as published by the Free
8
+ Software Foundation, either version 3 of the License, or (at your option) any
9
+ later version.
10
+
11
+ This project is distributed in the hope that it will be useful, but WITHOUT
12
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
14
+ details.
15
+
16
+ You should have received a copy of the GNU Affero General Public License along
17
+ with this project. If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.
18
+
19
+ The complete license text is available from the Free Software Foundation at:
20
+ https://www.gnu.org/licenses/agpl-3.0.txt
21
+
22
+ SPDX-License-Identifier: AGPL-3.0-or-later
data/NOTICE ADDED
@@ -0,0 +1,49 @@
1
+ jekyll-client-search — Test Fixture Attribution
2
+ ================================================
3
+
4
+ This repository includes test fixture posts derived from publicly available
5
+ sources. Each fixture post records its source, license, and a permanent link
6
+ in its YAML frontmatter.
7
+
8
+ Wikipedia articles (40 posts)
9
+ -----------------------------
10
+ Location: spec/fixtures/site/_posts/*-wikipedia-*.md
11
+ Source: Wikipedia — The Free Encyclopedia
12
+ License: CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/)
13
+ Link: Each post has a `wikipedia_oldid` field and a `source_url` pointing
14
+ to a permanent revision link (https://en.wikipedia.org/w/index.php?oldid=XXXX)
15
+ so the exact downloaded version can be referenced even as Wikipedia
16
+ articles change over time.
17
+
18
+ The download script (spec/fixtures/download_wikipedia.rb) is kept for
19
+ reference and regeneration but does not need to be re-run — the Wikipedia
20
+ fixtures are committed under their CC BY-SA 3.0 license with attribution.
21
+
22
+ arXiv papers (40 posts — NOT committed)
23
+ ---------------------------------------
24
+ Location: spec/fixtures/site/_posts/*-arxiv-*.md (gitignored)
25
+ Source: arXiv.org
26
+ License: Varies per paper — arXiv non-exclusive license, CC BY 4.0,
27
+ CC BY-NC-SA 4.0, or CC BY-NC-ND 4.0. Each paper's actual license
28
+ is recorded in its frontmatter by the download script.
29
+
30
+ arXiv fixture posts are NOT committed to this repository because some
31
+ papers use licenses that restrict redistribution (non-commercial, no
32
+ derivatives, or arXiv-only distribution rights). Developers who need the
33
+ full 80-post fixture set must run the download script locally:
34
+
35
+ ruby spec/fixtures/download_arxiv.rb
36
+
37
+ See README.developer.md for details.
38
+
39
+ Committed test artifacts
40
+ ------------------------
41
+ The following committed JSON files are generated test artifacts derived
42
+ from the fixture posts. They contain indexed/search-processed data (titles,
43
+ excerpts, tokenized content, embedding vectors), not the raw article text:
44
+
45
+ spec/fixtures/baseline/search-index-baseline.json
46
+ spec/fixtures/baseline/semantic-embeddings.json
47
+
48
+ These are kept as stable gold-standard fixtures so that tests can run
49
+ without requiring developers to download the arXiv papers or run Ollama.
@@ -0,0 +1,204 @@
1
+ # Developer Guide — jekyll-client-search
2
+
3
+ This guide explains how to set up the full test fixture set for local
4
+ development. Most tests run without any special setup, but a few require
5
+ fixture data that is not committed to the repository for licensing reasons.
6
+ See [README.md](README.md) for user-facing configuration and usage docs.
7
+
8
+ ## Quick start
9
+
10
+ ```bash
11
+ rbenv install 3.4.10 # if not already installed
12
+ rbenv local 3.4.10
13
+ bundle install
14
+ npm ci
15
+ bundle exec rspec # Ruby tests (176 examples; 7 opt-in Ollama examples pending)
16
+ npm test # JavaScript tests (130 tests)
17
+ ```
18
+
19
+ Both test suites pass with only the committed Wikipedia fixtures and the
20
+ committed baseline JSON artifacts. No network access or external services
21
+ are required for the default test run.
22
+
23
+ ## Build-time related articles
24
+
25
+ The optional related-analysis pass writes `search-relations.json` separately
26
+ from the normal search index. It combines exact shared tags/categories and
27
+ hierarchical category parents with vector similarity when embeddings are
28
+ available. Semantic relations use a configurable similarity cutoff; there is
29
+ no default five-item limit. `client-search-related.js` renders the relations
30
+ on an article page without loading an embedding model.
31
+
32
+ For a MiniSearch site, set `embedding.include_in_index: false` (the default
33
+ when the selected engine is not semantic) to use Ollama vectors during the
34
+ build and omit them from the public search index.
35
+
36
+ ### Adoption paths
37
+
38
+ Three adoption paths are documented in [README.md](README.md#related-articles):
39
+
40
+ 1. **`{% related_articles %}` Liquid tag** — one-line in any post layout
41
+ 2. **`{% include related-articles.html %}`** — reference include file shipped
42
+ at `assets/includes/related-articles.html`
43
+ 3. **Drop-in layout** — reference post layout shipped at
44
+ `assets/layouts/post-with-related.html`
45
+
46
+ The JS helper (`assets/client-search-related.js`) also supports `renderItem`
47
+ and `filter` callbacks for custom rendering and filtering.
48
+
49
+ ### Fixture site demo
50
+
51
+ The fixture site (`spec/fixtures/site/`) has related articles enabled in its
52
+ `_config.yml`. It includes:
53
+
54
+ - `_layouts/post.html` — minimal post layout using `{% related_articles %}`
55
+ - `related-test.html` — demo page at `/related-test/` exercising all five
56
+ variants: default Liquid tag, `sort:date` + `no_scripts`, custom
57
+ `renderItem`, `filter`, and raw JSON link
58
+
59
+ System tests in `spec/system_spec.rb` verify the relations file structure,
60
+ runtime asset copying, Liquid tag rendering in post pages, demo page
61
+ content, self-exclusion, and score sorting.
62
+
63
+ ### Related test files
64
+
65
+ | File | Description |
66
+ | --- | --- |
67
+ | `spec/related_analyzer_spec.rb` | Unit tests for the relation analyzer |
68
+ | `spec/related_tag_spec.rb` | Unit tests for the `{% related_articles %}` Liquid tag |
69
+ | `spec/search_tag_spec.rb` | Unit tests for the `{% search_form %}` Liquid tag |
70
+ | `spec/tasks_spec.rb` | Unit tests for the rake tasks (reference_files, install) |
71
+ | `spec/system_spec.rb` | System tests building the fixture site with related and search_form |
72
+ | `test/related.test.js` | JS tests for the related renderer (default, renderItem, filter, sort) |
73
+
74
+ ## What is committed
75
+
76
+ | Artifact | Committed? | Why |
77
+ |---|---|---|
78
+ | Wikipedia fixture posts (40) | Yes | CC BY-SA 3.0 allows redistribution with attribution |
79
+ | arXiv fixture posts (40) | **No** | Mixed/restrictive licenses — see below |
80
+ | Baseline search-index JSON | Yes | Generated test artifact (indexed data, not raw text) |
81
+ | Semantic embeddings JSON | Yes | Generated test artifact (embedding vectors) |
82
+ | Download scripts | Yes | Kept for reference and regeneration |
83
+
84
+ ## When you need the arXiv fixtures
85
+
86
+ The arXiv fixture posts are needed only for:
87
+
88
+ 1. **Ruby system tests that build the fixture site** — without arXiv posts,
89
+ these tests run with 40 Wikipedia posts instead of 80 and skip the
90
+ baseline comparison (the baseline was generated from the full 80-post
91
+ set).
92
+
93
+ 2. **Ollama integration tests** (`OLLAMA_INTEGRATION=1`) — these build the
94
+ full 80-post index with real embeddings and require both Wikipedia and
95
+ arXiv posts.
96
+
97
+ 3. **Regenerating the baseline JSON** — `ruby spec/fixtures/generate_baseline.rb`
98
+ requires all 80 posts.
99
+
100
+ ### Downloading arXiv fixtures
101
+
102
+ ```bash
103
+ ruby spec/fixtures/download_arxiv.rb
104
+ ```
105
+
106
+ This script:
107
+ - Fetches 40 recent arXiv papers from 5 CS/AI subfields
108
+ - Downloads each PDF and extracts text with `pdftotext`
109
+ - Records the actual per-paper license in each post's frontmatter
110
+ - Writes posts to `spec/fixtures/site/_posts/`
111
+ - Requires `pdftotext` (available via `brew install poppler` on macOS)
112
+
113
+ The arXiv API returns versioned papers (e.g. `2608.23419v1`). Running the
114
+ script at different times will return different papers, but a specific
115
+ version is permanent — arXiv never changes a published version.
116
+
117
+ ### Downloading Wikipedia fixtures (optional)
118
+
119
+ The Wikipedia fixtures are already committed. You only need this script if
120
+ you want to regenerate them (e.g. to get newer article versions):
121
+
122
+ ```bash
123
+ ruby spec/fixtures/download_wikipedia.rb
124
+ ```
125
+
126
+ Each Wikipedia post includes a `wikipedia_oldid` field with a permanent
127
+ link to the exact revision that was downloaded.
128
+
129
+ ## LLM / vector search testing
130
+
131
+ ### Without Ollama (default)
132
+
133
+ Semantic search tests run without a local Ollama server. They use committed
134
+ real-model embedding vectors (`spec/fixtures/baseline/semantic-embeddings.json`)
135
+ injected into the committed baseline index. This covers the browser-side
136
+ cosine similarity adapter and cross-engine comparison tests.
137
+
138
+ ### With Ollama (opt-in integration tests)
139
+
140
+ To run the full integration tests that call a real Ollama server:
141
+
142
+ 1. Install [Ollama](https://ollama.ai/)
143
+
144
+ 2. Pull the required model:
145
+
146
+ ```bash
147
+ ollama pull embeddinggemma:300m
148
+ ```
149
+
150
+ 3. Run the integration tests:
151
+
152
+ ```bash
153
+ OLLAMA_INTEGRATION=1 bundle exec rspec spec/ollama_integration_spec.rb
154
+ OLLAMA_INTEGRATION=1 bundle exec rspec spec/llm_injection_spec.rb
155
+ ```
156
+
157
+ These tests:
158
+ - Build the full 80-post index with real Ollama embeddings
159
+ - Verify embedding dimensions and validity
160
+ - Test semantic search quality with concept queries
161
+ - Generate the semantic index used by JS comparison tests
162
+ - Require arXiv fixture posts to be downloaded first
163
+
164
+ ### Regenerating committed test artifacts
165
+
166
+ If you need to regenerate the baseline or semantic gold fixtures (e.g.
167
+ after changing the document builder or fixture content):
168
+
169
+ ```bash
170
+ # 1. Ensure all 80 fixture posts are present
171
+ ruby spec/fixtures/download_arxiv.rb
172
+
173
+ # 2. Regenerate the baseline index (without embeddings)
174
+ ruby spec/fixtures/generate_baseline.rb
175
+
176
+ # 3. Run the Ollama integration test to generate the semantic index
177
+ OLLAMA_INTEGRATION=1 bundle exec rspec spec/ollama_integration_spec.rb
178
+
179
+ # 4. Regenerate the semantic gold fixture from the built index
180
+ ruby spec/fixtures/generate_semantic_gold.rb
181
+ ```
182
+
183
+ ## Test categories
184
+
185
+ | Tag | Description | Requires arXiv? | Requires Ollama? |
186
+ |---|---|---|---|
187
+ | `:unit` | Ruby unit tests | No | No |
188
+ | `:system` | Ruby system tests (build fixture site) | Partially | No |
189
+ | `:integration` | Ruby integration tests | No | No |
190
+ | `:ollama_integration` | Real Ollama server tests | Yes | Yes |
191
+
192
+ Run a specific category:
193
+
194
+ ```bash
195
+ bundle exec rspec --tag unit
196
+ bundle exec rspec --tag system
197
+ bundle exec rspec --tag ollama_integration
198
+ ```
199
+
200
+ ## CI
201
+
202
+ The CI pipeline runs `bundle exec rspec` and `npm test` without arXiv
203
+ fixtures or Ollama. The committed baseline JSON and semantic embeddings
204
+ ensure all tests pass in CI without external dependencies.