Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
edf737c
Use released dependencies in the MobileSAM Learning Path
usamahz Sep 29, 2026
dbab932
Use ExecuTorch 1.5.1 in the Silero VAD Learning Path
usamahz Sep 29, 2026
7a47fae
Simplify the MobileSAM dependency setup guidance
usamahz Sep 29, 2026
aaa258e
Simplify the Silero dependency setup guidance
usamahz Sep 29, 2026
7d71434
Verify MobileSAM setup and clarify FVP validation
usamahz Oct 13, 2026
25a6620
Clarify Silero validation state limitation
usamahz Oct 13, 2026
c61eb13
Review and improve ML Extensions for Vulkan Learning Path
BarbaraCorrieroArm Oct 15, 2026
8f3fac7
Clarify Vulkan ML architecture and improve Learning Path setup
BarbaraCorrieroArm Oct 20, 2026
71c7fda
Expand Scenario Runner exercise and improve RenderDoc workflow
BarbaraCorrieroArm Oct 20, 2026
e711e71
Add browser search trial with WebMCP agent tools
jasonrandrews Oct 29, 2026
5eb2fb9
assisted pass
anupras-mohapatra-arm Oct 30, 2026
402dabb
adding summary
anupras-mohapatra-arm Oct 30, 2026
e77e12e
edits
anupras-mohapatra-arm Oct 30, 2026
bc29e58
edits
anupras-mohapatra-arm Oct 30, 2026
8cd6db0
edits
anupras-mohapatra-arm Oct 30, 2026
d594ac5
Align Silero VAD Learning Path with the E2E example
usamahz Nov 2, 2026
c780110
Align MobileSAM Learning Path with the E2E example
usamahz Nov 2, 2026
be402af
Merge remote-tracking branch 'refs/remotes/codex-learner/main' into c…
usamahz Nov 2, 2026
2f11196
Squash commit - inital draft for review
kieranhejmadi01 Oct 14, 2026
efe11b2
fix - moved compute-flow to cross-platform category discoverable thro…
kieranhejmadi01 Oct 26, 2026
a04982b
improvement - add troubleshooting guidance for dependency compatibili…
kieranhejmadi01 Oct 26, 2026
da0bdad
reorder to use unquantized moonshine-base-en first
kieranhejmadi01 Oct 26, 2026
324c849
Revert "reorder to use unquantized moonshine-base-en first"
kieranhejmadi01 Oct 26, 2026
06e73fb
add subheading section around using cli to profile and experiment qui…
kieranhejmadi01 Oct 26, 2026
e6097fa
prioritize fixing potential dependency issues on system before buildi…
kieranhejmadi01 Oct 26, 2026
3334efa
replace with actual public URL
kieranhejmadi01 Oct 27, 2026
2a4d8b5
incorporate feedback on _index.md
kieranhejmadi01 Oct 29, 2026
b81376b
incorporate feedback on how-to-1.md
kieranhejmadi01 Oct 29, 2026
f3c1b36
incorporate feedback in how-to-2.md
kieranhejmadi01 Oct 29, 2026
e5f804c
breakout final section into 2 pages and incorporate feedback
kieranhejmadi01 Oct 29, 2026
50b489f
compute flow first pass
anupras-mohapatra-arm Oct 30, 2026
fba0897
add verified french translation by native speaker
kieranhejmadi01 Nov 2, 2026
5294556
fix accidental revert during copy edit
kieranhejmadi01 Nov 2, 2026
911a15d
Fix Silero learner build limits and macOS FVP path
usamahz Nov 2, 2026
88ba916
Pin the tested macOS FVP setup for MobileSAM
usamahz Nov 2, 2026
89fdcce
c2patool first pass
anupras-mohapatra-arm Nov 2, 2026
e4c4c13
assisted edits
anupras-mohapatra-arm Nov 2, 2026
fc245d1
Update Scenario Runner compatibility and CLI commands
BarbaraCorrieroArm Nov 2, 2026
5a01fda
exiftool first pass
anupras-mohapatra-arm Nov 2, 2026
c82d480
nit
anupras-mohapatra-arm Nov 2, 2026
08700d2
edits
anupras-mohapatra-arm Nov 2, 2026
4b3ccb7
edits
anupras-mohapatra-arm Nov 2, 2026
afd2d98
work on early access search feature
jasonrandrews Nov 2, 2026
5824030
modify testing for changed .md tests to focus on learning-paths and i…
jasonrandrews Nov 3, 2026
02027f5
Merge pull request #3989 from jasonrandrews/review3
jasonrandrews Nov 3, 2026
e11d8c4
add opencv-onnx-kleidicv-static-detection
jtzhou-001 Sep 16, 2026
99d2863
add validated support for linux-arm64 native
kieranhejmadi01 Nov 3, 2026
9da14af
add support for android cross-compilation target
kieranhejmadi01 Nov 3, 2026
84e1a59
Merge pull request #3985 from anupras-mohapatra-arm/install-guides
pareenaverma Nov 3, 2026
306a616
Updates to early access search feature
jasonrandrews Nov 3, 2026
ac74e29
Merge pull request #3992 from jasonrandrews/review3
jasonrandrews Nov 3, 2026
153340b
nit
anupras-mohapatra-arm Nov 3, 2026
4323058
initial draft of opensandbox install guide
anupras-mohapatra-arm Nov 3, 2026
c924f52
nit
anupras-mohapatra-arm Nov 3, 2026
6f549e0
adding JSON outputs
anupras-mohapatra-arm Nov 3, 2026
356eaab
adding JSON output
anupras-mohapatra-arm Nov 3, 2026
bfc8b35
adding sentence for shutting the running server
anupras-mohapatra-arm Nov 3, 2026
e1a20e9
updating titles that are too long
anupras-mohapatra-arm Nov 3, 2026
aba17f8
Tech review of libjpeg install guide
pareenaverma Nov 3, 2026
58f3ca1
Merge pull request #3994 from pareenaverma/content_review
pareenaverma Nov 3, 2026
95f4e39
Use production chatbot script
jaidev17 Nov 3, 2026
8fc3f08
Merge pull request #3995 from jaidev17/fix/chatbot-production-script
jasonrandrews Nov 3, 2026
827c7c0
edits to updated titles
anupras-mohapatra-arm Nov 3, 2026
d81138c
LP: Innexis ANA Zena CPU llm build and inference
odinshen-git Nov 3, 2026
2ac2bab
first pass libjpeg-turbo
anupras-mohapatra-arm Nov 3, 2026
119f0b0
edits
anupras-mohapatra-arm Nov 3, 2026
2c08159
nit
anupras-mohapatra-arm Nov 3, 2026
01afbca
nit
anupras-mohapatra-arm Nov 3, 2026
debc1e1
Merge pull request #3998 from anupras-mohapatra-arm/main
jasonrandrews Nov 3, 2026
5d6432a
LP: Innexis ANA Zena CPU llm build and inference
odinshen-git Nov 3, 2026
0e47ec8
Merge pull request #3996 from anupras-mohapatra-arm/fixes
jasonrandrews Nov 4, 2026
65a3ac3
Merge pull request #3993 from anupras-mohapatra-arm/install-guides
jasonrandrews Nov 4, 2026
3db62b0
updating images with empty alt-text
anupras-mohapatra-arm Nov 4, 2026
07b316e
updating gitignore to exclude summary and FAQ reports
anupras-mohapatra-arm Nov 4, 2026
9624252
removing extra quotations
anupras-mohapatra-arm Nov 4, 2026
56b7ec1
review OpenSandbox install guide
jasonrandrews Nov 4, 2026
1f4bf63
first batch of Azure monitoring alt-text fixes
anupras-mohapatra-arm Nov 4, 2026
b207a77
more Azure monitoring alt-text fixes
anupras-mohapatra-arm Nov 4, 2026
bcaa629
Merge pull request #4001 from jasonrandrews/review3
jasonrandrews Nov 4, 2026
4856e9c
Merge pull request #4002 from anupras-mohapatra-arm/fixes
jasonrandrews Nov 4, 2026
4c4a896
Update _index.md to draft mode
jasonrandrews Nov 4, 2026
fe46673
first pass edits to opensandbox
anupras-mohapatra-arm Nov 4, 2026
92b3cd3
Merge pull request #3990 from jtzhou-001/opencv-onnx-kleidicv-static-…
jasonrandrews Nov 4, 2026
2ffb6a5
adding search terms and other nits
anupras-mohapatra-arm Nov 4, 2026
d9f2bb6
nit
anupras-mohapatra-arm Nov 4, 2026
a0eee64
updating subhead to reflect both deletion and shutting the server
anupras-mohapatra-arm Nov 4, 2026
c3bca8a
nit
anupras-mohapatra-arm Nov 4, 2026
0d1d67c
updating outputs to use output tags
anupras-mohapatra-arm Nov 4, 2026
53609ec
nit
anupras-mohapatra-arm Nov 4, 2026
e14913b
Merge pull request #4004 from anupras-mohapatra-arm/install-guides
jasonrandrews Nov 4, 2026
0d634df
small change to not take cachedAccount[0] but only accept correct pol…
chrismoroney Nov 4, 2026
1385fd0
Merge pull request #4005 from ArmDeveloperEcosystem/b2c-update-compli…
pareenaverma Nov 4, 2026
0dd8937
uv_debug alt-text updates
anupras-mohapatra-arm Nov 4, 2026
4de906d
Download Muse Glimmer exercise assets from GitLab archive
matt-cossins Nov 5, 2026
12e6a48
wasmCloud Learning Path
jasonrandrews Nov 5, 2026
86229cd
Merge pull request #4011 from jasonrandrews/review3
jasonrandrews Nov 5, 2026
1e76a56
wasmCloud install guide
jasonrandrews Nov 5, 2026
f74e13a
Merge pull request #4012 from jasonrandrews/review3
jasonrandrews Nov 5, 2026
48dbcff
Update _index.md
pareenaverma Nov 5, 2026
32cf827
Clarify Mac architecture in summary
pareenaverma Nov 5, 2026
739610f
first pass on install guide
anupras-mohapatra-arm Nov 5, 2026
d1ccf3b
Merge pull request #3980 from kieranhejmadi01/compute_flow_getting_st…
pareenaverma Nov 5, 2026
f0caa6c
implement experimental search feedback
jasonrandrews Nov 5, 2026
d448ce6
Merge pull request #4013 from jasonrandrews/review3
jasonrandrews Nov 5, 2026
7fac52a
LP: Innexis ANA Zena onboarding
odinshen-git Nov 5, 2026
a4bab17
Add ETRecord/ETDump MLIA Model Explorer section
annietllnd Nov 5, 2026
9709a3a
additional edits for compute flow
anupras-mohapatra-arm Nov 5, 2026
2d749be
nit
anupras-mohapatra-arm Nov 5, 2026
f103881
Add Barbara Corriero as author
BarbaraCorrieroArm Nov 5, 2026
c843d13
edits
anupras-mohapatra-arm Nov 5, 2026
a18be5b
index edits
anupras-mohapatra-arm Nov 5, 2026
9641389
nit
anupras-mohapatra-arm Nov 5, 2026
b5bb728
wasmCloud LP first pass
anupras-mohapatra-arm Nov 5, 2026
4033096
assisted edits
anupras-mohapatra-arm Nov 5, 2026
52bf70c
adding summary and FAQs
anupras-mohapatra-arm Nov 5, 2026
baac2ff
c2pa tech review
pareenaverma Nov 6, 2026
6aeddef
Merge pull request #4019 from pareenaverma/content_review
pareenaverma Nov 6, 2026
7918248
Clarify MLIA profiling guidance from PR review
annietllnd Nov 6, 2026
25d8ff4
Update _index.md draft
jasonrandrews Nov 6, 2026
bce6359
Merge pull request #3997 from odincodeshen/feature/innexis_zena_llamacpp
jasonrandrews Nov 6, 2026
883f4b7
edits
anupras-mohapatra-arm Nov 6, 2026
46ffd85
removing draft flag from install guide
anupras-mohapatra-arm Nov 6, 2026
396638a
Merge pull request #4009 from anupras-mohapatra-arm/fixes
jasonrandrews Nov 6, 2026
6f77ca7
Update _index.md draft
jasonrandrews Nov 6, 2026
4f8b819
Merge pull request #4017 from anupras-mohapatra-arm/cross-platform
pareenaverma Nov 6, 2026
3332828
Merge pull request #4014 from odincodeshen/feature/innexis_zena_onboa…
jasonrandrews Nov 6, 2026
27b38c7
Merge pull request #4000 from anupras-mohapatra-arm/main
pareenaverma Nov 6, 2026
6a35bbf
Merge pull request #4016 from BarbaraCorrieroArm/update-vulkan-ml-lea…
pareenaverma Nov 6, 2026
f8ddf6d
installing curl before running install script in install guide
anupras-mohapatra-arm Nov 6, 2026
4970cec
assisted edits
anupras-mohapatra-arm Nov 6, 2026
4eff3af
assisted edits
anupras-mohapatra-arm Nov 6, 2026
9f7d45a
restoring an original title
anupras-mohapatra-arm Nov 6, 2026
2217f1a
Merge pull request #4025 from anupras-mohapatra-arm/servers-and-cloud…
pareenaverma Nov 6, 2026
864749c
Merge pull request #4015 from annietllnd/mlia-update
pareenaverma Nov 6, 2026
9504d38
Merge pull request #3824 from usamahz/codex/mobilesam-stable-dependen…
pareenaverma Nov 6, 2026
e088a5c
Merge pull request #3825 from usamahz/codex/silero-stable-dependencies
pareenaverma Nov 6, 2026
c202fdf
Merge pull request #4010 from matt-cossins/muse-glimmer-gitlab-assets
pareenaverma Nov 6, 2026
8060318
Update .wordlist.txt
pareenaverma Nov 6, 2026
75a5f7b
Link fix
pareenaverma Nov 6, 2026
fc78238
Merge branch 'content_review' of https://github.com/pareenaverma/arm-…
pareenaverma Nov 6, 2026
a506ff8
Merge pull request #4027 from pareenaverma/content_review
pareenaverma Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 6 additions & 5 deletions .github/workflows/test-lp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ jobs:
- name: Run hugo command to test site builds
run: |
hugo
- name: Get all changed markdown files
- name: Get changed Learning Path and install guide markdown files
id: changed-markdown-files
uses: tj-actions/changed-files@v46
with:
files: |
content/**/**.md
content/learning-paths/**/*.md
content/install-guides/**/*.md
- name: Check for capital letters or spaces in content directory
run: |
echo "Checking for capital letters or spaces in content directory paths (excluding file extensions)..."
Expand Down Expand Up @@ -63,15 +64,15 @@ jobs:
run: |
echo "Checking YAML fields for changed learning paths..."
python3 tools/verify_index_fields.py ${{ steps.changed-markdown-files.outputs.all_changed_files }}
- name: Run test suite for all changed .md files
- name: Run test suite for changed Learning Paths and install guides
id: run-suite
if: steps.changed-markdown-files.outputs.any_changed == 'true'
# Run the test suite
run: |
set -o pipefail; ./tools/test_lp.sh ${{ steps.changed-markdown-files.outputs.all_changed_files }} 2>&1 | tee test-lp-output.txt
- name: Parse test suite errors
id: test-suite-state
if: success()
if: success() && steps.changed-markdown-files.outputs.any_changed == 'true'
# Catch any missed errors if running multiple tests
run: |
cat test-lp-output.txt | grep -q 'Tests failed in test suite' && echo "TEST_SUITE_ERRORS=true" >> "$GITHUB_ENV" \
Expand All @@ -83,7 +84,7 @@ jobs:
exit 1
- name: Parse test maintenance off
id: maintenance-state
if: success()
if: success() && steps.changed-markdown-files.outputs.any_changed == 'true'
# Check if maintenance is turned off
run: |
cat test-lp-output.txt | grep -q 'maintenance is turned off' && echo "MAINTENANCE=off" >> "$GITHUB_ENV" \
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/tools/social_card_generation/.social-card-manifest.json*
__pycache__/
resources/
reports/
node_modules/
package-lock.json
.hugo_build.lock
Expand Down
30 changes: 30 additions & 0 deletions .wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7655,3 +7655,33 @@ Andreas
COFF
disassemblers
otool
EventRecord
EventRecorderConf
ExifTool
GLIBCXX
GLib
Kostiantyn
Liam
OpenSandbox
Perehuda
StartA
StopA
WASI
Wasm
WebAssembly
arewefastyet
bdec
bffefbea
contentauth
dialogs
efd
exiftool
labelled
libjpeg
libstdc
opensandbox
osb
patool
wasmCloud
wasmcloud
wg
1 change: 1 addition & 0 deletions assets/contributors.csv
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,4 @@ Sammy Sahnine,,,,,
Matthew Sloyan,Arm,matthewsloyanARM,,,
Austin Hill,,,,,
Andreas Mullen,Arm,,,,
Liam Randall,,,,,
169 changes: 169 additions & 0 deletions assets/search-lab/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const $ = id => document.getElementById(id);
const main = document.querySelector('main[data-index]');
let worker, workerFailure;
let allowBlankSearch = false;
const searchStop = new Set('a an and are at be can do for from how i in is it me my of on or the to using what with'.split(' '));
let nextId = 0, generation = 0, offset = 0;
const pending = new Map();
function failWorker() {
workerFailure = Error('Search is unavailable. Reload the page to retry.');
worker?.terminate();
++generation;
for (const request of pending.values()) request.reject(workerFailure);
pending.clear();
$('more').hidden = true;
$('search-diagnostics').hidden = true;
$('status').textContent = workerFailure.message;
}
try {
worker = new Worker(main.dataset.worker, { type: 'module' });
worker.onmessage = ({ data }) => {
const request = pending.get(data.id);
if (!request) return;
pending.delete(data.id);
data.error ? request.reject(Error(data.error)) : request.resolve(data.result);
};
worker.onerror = failWorker;
worker.onmessageerror = failWorker;
} catch { failWorker(); }
function call(action, input) {
if (workerFailure) return Promise.reject(workerFailure);
return new Promise((resolve, reject) => {
const id = ++nextId;
pending.set(id, { resolve, reject });
try { worker.postMessage({ id, action, input, url: main.dataset.index }); }
catch { failWorker(); }
});
}
const ready = call('init');
const tool = {
name: 'search_arm_content',
description: 'Find Arm Learning Paths and install guides by keyword or author name, with word prefixes and limited typo tolerance. Relevance defaults to complete query matches first. Returns source URLs and metadata excerpts; use these sources to read the full instructions. Scores indicate keyword relevance, not confidence.',
inputSchema: { type: 'object', properties: {
query: { type: 'string', maxLength: 300, description: 'Leave empty to browse all content matching the filters, newest first.' },
type: { type: 'string', enum: ['', 'learning-path', 'install-guide'], default: '' },
category: { type: 'string', description: 'Learning Path category slug, such as servers-and-cloud-computing. Ignored when type is install-guide.' },
sort: { type: 'string', enum: ['relevance', 'updated'], default: 'relevance' },
offset: { type: 'integer', minimum: 0, default: 0, description: 'Number of results to skip for pagination.' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }
}, required: ['query'], additionalProperties: false },
annotations: { readOnlyHint: true },
async execute(input, { signal } = {}) {
try {
signal?.throwIfAborted();
await ready;
signal?.throwIfAborted();
const result = await call('search', input);
signal?.throwIfAborted();
return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result };
} catch (error) {
if (signal?.aborted) throw error;
return { isError: true, content: [{ type: 'text', text: error.message }] };
}
}
};
// The tester and the registered tool invoke precisely the same callback.
$('run-tool').onclick = async () => {
try { $('tool-output').textContent = JSON.stringify(await tool.execute(JSON.parse($('tool-input').value)), null, 2); }
catch (error) { $('tool-output').textContent = error.message; }
};
async function register() {
const context = document.modelContext || navigator.modelContext;
if (!context?.registerTool) {
$('webmcp').textContent = 'Native WebMCP is unavailable in this browser. Use the tool tester below.';
return;
}
try {
await context.registerTool(tool);
$('webmcp').textContent = 'WebMCP registered: search_arm_content';
} catch (error) { $('webmcp').textContent = `WebMCP registration failed: ${error.message}. The tester still works.`; }
}
register();
async function render() {
const current = ++generation;
const query = $('query').value.trim();
$('clear-blank-search').hidden = !allowBlankSearch || $('query').value !== '';
$('category').disabled = $('type').value === 'install-guide';
if ($('category').disabled) $('category').value = '';
const params = new URLSearchParams();
if (query) params.set('q', query);
if ($('type').value) params.set('type', $('type').value);
if ($('category').value) params.set('category', $('category').value);
params.set('sort', $('sort').value);
history.replaceState(null, '', `${location.pathname}${params.size ? '?' + params : ''}${location.hash}`);
if (!offset) $('results').replaceChildren();
$('more').hidden = true;
$('search-diagnostics').hidden = true;
if (workerFailure) { $('status').textContent = workerFailure.message; return; }
if (!query && !allowBlankSearch) {
$('status').textContent = 'Enter a search term, or click Search to browse all content matching your filters.';
return;
}
if (query && !(query.toLowerCase().match(/[a-z0-9]+/g) || []).some(w => !searchStop.has(w))) {
$('status').textContent = `"${query}" contains only common words that search ignores. Try adding a more specific term.`;
return;
}
$('status').textContent = 'Searching…';
const start = performance.now();
try {
await ready;
const result = await call('search', { query, type: $('type').value, category: $('category').value, sort: $('sort').value, limit: 10, offset });
if (current !== generation) return;
$('status').textContent = `${result.total} result${result.total === 1 ? '' : 's'}${query ? ` for “${query}”` : ' · newest first'}`;
$('performance').textContent = `Last search: ${result.searchMs.toFixed(1)} ms engine · ${(performance.now() - start).toFixed(1)} ms response`;
$('search-diagnostics').hidden = false;
if (!result.total) $('status').textContent += query ? '. Try fewer words or remove a filter.' : '. Try removing a filter.';
if (result.total) $('status').textContent += `. Showing ${offset + result.results.length} of ${result.total}.`;
for (const record of result.results) {
const li = document.createElement('li'); li.className = 'search-result-card';
const a = document.createElement('a'); a.href = record.url; a.textContent = record.title;
const h = document.createElement('h3'); h.className = 'result-title'; h.append(a);
const meta = document.createElement('p'); meta.className = 'result-meta';
const badge = document.createElement('span'); badge.className = 'result-type';
badge.textContent = record.type === 'learning-path' ? 'Learning Path' : 'Install guide'; meta.append(badge);
if (record.category) { const category = document.createElement('span'); category.textContent = categoryLabel(record.category); meta.append(category); }
const p = document.createElement('p'); p.className = 'result-description'; p.textContent = record.description || record.summary;
const footer = document.createElement('div'); footer.className = 'result-footer';
if (record.authors?.length) { const authors = document.createElement('p'); authors.textContent = 'By ' + record.authors.join(', '); footer.append(authors); }
const updated = document.createElement('p');
if (record.lastUpdated) {
const time = document.createElement('time'); time.dateTime = record.lastUpdated;
time.textContent = new Date(record.lastUpdated + 'T00:00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' });
updated.append('Last updated: ', time);
} else { updated.textContent = 'Last updated: unavailable'; }
footer.append(updated); li.append(meta, h, p, footer); $('results').append(li);
}
$('more').hidden = result.total <= offset + result.results.length;
if (offset && result.results.length) { const firstNew = $('results').children[offset]; firstNew?.querySelector('a')?.focus(); }
} catch (error) { if (current === generation) $('status').textContent = error.message; }
}
function categoryLabel(slug) {
return slug.split('-').map((word, index) => index && ['and'].includes(word) ? word : word[0].toUpperCase() + word.slice(1)).join(' ');
}
for (const button of main.querySelectorAll('[data-query]')) button.onclick = () => {
$('query').value = button.dataset.query; $('type').value = ''; $('category').value = ''; offset = 0; render();
};
let timer;
$('query').oninput = () => { $('clear-blank-search').hidden = true; allowBlankSearch = false; ++generation; $('more').hidden = true; clearTimeout(timer); $('results').replaceChildren(); $('search-diagnostics').hidden = true; if (!$('query').value) { offset = 0; $('status').textContent = 'Enter a search term, or click Search to browse all content matching your filters.'; } else { $('status').textContent = ''; } };
$('search-form').onsubmit = event => { event.preventDefault(); allowBlankSearch = true; clearTimeout(timer); offset = 0; render(); };
$('clear-blank-search').onclick = () => {
clearTimeout(timer);
allowBlankSearch = false;
$('query').value = '';
offset = 0;
render();
$('query').focus();
};
for (const id of ['type', 'category', 'sort']) $(id).onchange = () => { if (!$('query').value.trim()) allowBlankSearch = true; offset = 0; render(); };
$('more').onclick = () => { offset += 10; render(); };
const params = new URLSearchParams(location.search);
$('query').value = (params.get('q') || '').slice(0, 300);
if (['updated', 'relevance'].includes(params.get('sort'))) $('sort').value = params.get('sort');
if (['learning-path', 'install-guide'].includes(params.get('type'))) $('type').value = params.get('type');
try {
const info = await ready;
for (const category of info.categories) { const option = document.createElement('option'); option.value = category; option.textContent = categoryLabel(category); $('category').append(option); }
if (info.categories.includes(params.get('category'))) $('category').value = params.get('category');
$('metrics').textContent = `${info.count} documents · ${(info.bytes / 1024).toFixed(0)} KB index (uncompressed) · ${info.loadMs.toFixed(0)} ms to download and prepare`;
render();
} catch (error) { $('status').textContent = `Unable to load search: ${error.message} Reload to retry.`; }
102 changes: 102 additions & 0 deletions assets/search-lab/engine.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Dependency-free BM25 baseline. Exact identifiers stay searchable alongside words.
const stop = new Set('a an and are at be can do for from how i in is it me my of on or the to using what with'.split(' '));
const aliases = new Map([['cpp', 'c++'], ['csharp', 'c#']]);
export function tokenize(text) {
const tokens = String(text).toLowerCase().match(/[\p{L}\p{N}]+(?:[.+#-][\p{L}\p{N}+#]+)*#?/gu) || [];
return tokens.map(token => aliases.get(token) || token);
}
// One insertion, deletion, substitution, or adjacent transposition.
function oneEditAway(a, b) {
if (Math.abs(a.length - b.length) > 1) return false;
let i = 0;
while (i < a.length && a[i] === b[i]) i++;
if (i === a.length) return b.length === a.length + 1;
if (a.length === b.length) {
return a.slice(i + 1) === b.slice(i + 1)
|| (a[i] === b[i + 1] && a[i + 1] === b[i] && a.slice(i + 2) === b.slice(i + 2));
}
return a.length > b.length ? a.slice(i + 1) === b.slice(i) : a.slice(i) === b.slice(i + 1);
}
export function createEngine(records) {
const fields = [['title', 5], ['description', 3], ['authors', 3], ['metadata', 2], ['prerequisites', 2], ['summary', 1], ['faq', 1]];
const docs = records.map(record => ({ record, fields: fields.map(([key, boost]) => {
const value = record[key] || '';
const tokens = tokenize(Array.isArray(value) ? value.join(' ') : value);
const counts = new Map();
for (const token of tokens) counts.set(token, (counts.get(token) || 0) + 1);
return { counts, length: tokens.length, boost };
}) }));
const averages = fields.map((_, i) => docs.reduce((n, d) => n + d.fields[i].length, 0) / (docs.length || 1) || 1);
const df = new Map();
for (const doc of docs) {
const terms = new Set(doc.fields.flatMap(f => [...f.counts.keys()]));
for (const term of terms) df.set(term, (df.get(term) || 0) + 1);
}
const vocabulary = [...df.keys()];
return function search(input = {}) {
if (!input || typeof input !== 'object' || Array.isArray(input)) throw Error('Arguments must be an object.');
if (Object.keys(input).some(key => !['query', 'type', 'category', 'limit', 'offset', 'sort'].includes(key))) throw Error('Unknown search argument.');
const { query, type = '', category = '', limit = 10, offset = 0, sort = 'relevance' } = input;
if (typeof query !== 'string' || query.length > 300) throw Error('Query must be a string of at most 300 characters.');
if (!['', 'learning-path', 'install-guide'].includes(type)) throw Error('Unknown content type.');
if (typeof category !== 'string' || (category && !records.some(r => r.category === category))) throw Error('Unknown category.');
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw Error('Limit must be an integer from 1 to 100.');
if (!Number.isInteger(offset) || offset < 0) throw Error('Offset must be a non-negative integer.');
if (!['updated', 'relevance'].includes(sort)) throw Error('Unknown sort order.');
const terms = [...new Set(tokenize(query).filter(t => !stop.has(t)))];
const browsing = !query.trim();
if (!browsing && !terms.length) return { total: 0, results: [] };
const expansions = terms.map(term => {
const candidates = [{ token: term, weight: 1 }];
if (/^\p{L}{3,}$/u.test(term)) {
for (const token of vocabulary) {
if (token !== term && token.startsWith(term)) candidates.push({ token, weight: 0.65 });
}
// Correct a typo only when there is no exact token or prefix completion.
if (!df.has(term) && candidates.length === 1 && /^\p{L}{5,}$/u.test(term)) {
for (const token of vocabulary) {
if (/^\p{L}{5,}$/u.test(token) && oneEditAway(term, token)) candidates.push({ token, weight: 0.4 });
}
}
}
return candidates.map(candidate => ({ ...candidate,
idf: Math.log(1 + (docs.length - (df.get(candidate.token) || 0) + 0.5) / ((df.get(candidate.token) || 0) + 0.5))
}));
});
const ranked = [];
for (const doc of docs) {
if ((type && doc.record.type !== type) || (type !== 'install-guide' && category && doc.record.category !== category)) continue;
if (browsing) {
ranked.push({ record: { ...doc.record, score: 0 }, tier: 0 });
continue;
}
let score = 0, matched = 0, exact = 0;
for (const candidates of expansions) {
let termScore = 0;
let exactMatch = false;
doc.fields.forEach((field, i) => {
let best = 0;
for (const { token, weight, idf } of candidates) {
const tf = field.counts.get(token) || 0;
if (weight === 1 && tf) exactMatch = true;
best = Math.max(best, weight * field.boost * idf * tf * 2.2 / (tf + 1.2 * (0.25 + 0.75 * field.length / averages[i])));
}
termScore += best;
});
if (termScore) matched++;
if (exactMatch) exact++;
score += termScore;
}
// Reward coverage so long natural-language queries can still find partial matches.
if (matched) ranked.push({ record: { ...doc.record, score: score * (matched / terms.length) ** 2 },
tier: exact === terms.length ? 2 : matched === terms.length ? 1 : 0 });
}
ranked.sort((a, b) => {
// ISO date strings sort chronologically; missing dates follow dated results.
const dateOrder = (browsing || sort === 'updated') ? (b.record.lastUpdated || '').localeCompare(a.record.lastUpdated || '') : 0;
const coverageOrder = sort === 'relevance' ? b.tier - a.tier : 0;
return dateOrder || coverageOrder || b.record.score - a.record.score || a.record.id.localeCompare(b.record.id);
});
return { total: ranked.length, results: ranked.slice(offset, offset + limit).map(item => item.record) };
};
}
Loading
Loading