From 077c41844ddd9a97a701933bf465e8e3a3a02292 Mon Sep 17 00:00:00 2001
From: William Mantly
Date: Fri, 17 Jul 2026 19:22:27 -0400
Subject: [PATCH] Add header help icon and in-app docs search
- A ? icon in the top-right header deep-links to the doc most relevant to
the current page (client-side path mapping, same pattern already used
for top-nav active-link highlighting -- no server-side "current section"
local exists to key off of instead). Falls back to the docs index.
- GET /docs/search does a plain line-substring search over the existing
allowlisted doc set. No new dependency, stays usable with no internet
access.
Bumps to v1.1.10.
Co-Authored-By: Claude Sonnet 5
Claude-Session: https://claude.ai/code/session_01KDEx8ghuZR61pqPXc6da9C
---
CHANGELOG.md | 9 +++++++-
nodejs/package-lock.json | 4 ++--
nodejs/package.json | 2 +-
nodejs/routes/docs.js | 24 +++++++++++++++++++++
nodejs/views/docs_index.ejs | 43 ++++++++++++++++++++++++++++++++++++-
nodejs/views/top.ejs | 22 +++++++++++++++++++
6 files changed, 99 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c32a170..a2933b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
## [Unreleased]
+## [1.1.10] - 2026-07-17
+
+### Added
+- A help icon (❓) in the top-right header now deep-links to the doc most relevant to the current page (falls back to the docs index elsewhere).
+- The in-app docs viewer (`/docs`) is now searchable — a simple line-substring search over the same local doc set, no new dependency, still works with no internet access.
+
## [1.1.9] - 2026-07-17
### Added
@@ -77,7 +83,8 @@ First tagged release. Establishes the `vX.Y.Z` tag convention that the in-app up
- Unix/POSIX and LDAP bind-only service account support, distinct from real-person accounts.
- Merged OAuth Apps + LDAP Info into a single Integrations page.
-[Unreleased]: https://github.com/theta42/sso-manager-node/compare/v1.1.9...HEAD
+[Unreleased]: https://github.com/theta42/sso-manager-node/compare/v1.1.10...HEAD
+[1.1.10]: https://github.com/theta42/sso-manager-node/compare/v1.1.9...v1.1.10
[1.1.9]: https://github.com/theta42/sso-manager-node/compare/v1.1.8...v1.1.9
[1.1.8]: https://github.com/theta42/sso-manager-node/compare/v1.1.7...v1.1.8
[1.1.7]: https://github.com/theta42/sso-manager-node/compare/v1.1.6...v1.1.7
diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json
index a2bc4a2..bf7b15e 100644
--- a/nodejs/package-lock.json
+++ b/nodejs/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "t42-sso-manager",
- "version": "1.1.9",
+ "version": "1.1.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "t42-sso-manager",
- "version": "1.1.9",
+ "version": "1.1.10",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
diff --git a/nodejs/package.json b/nodejs/package.json
index 8af4bc7..3e3798e 100755
--- a/nodejs/package.json
+++ b/nodejs/package.json
@@ -1,6 +1,6 @@
{
"name": "t42-sso-manager",
- "version": "1.1.9",
+ "version": "1.1.10",
"private": true,
"author": [
{
diff --git a/nodejs/routes/docs.js b/nodejs/routes/docs.js
index 2c67733..5e36439 100644
--- a/nodejs/routes/docs.js
+++ b/nodejs/routes/docs.js
@@ -52,6 +52,30 @@ router.get('/', function(req, res) {
res.render('docs_index', {...values, docs: docList});
});
+// Plain, dependency-free line-substring search over the same allowlisted
+// doc set -- no separate index to build/maintain, no new dependency, and it
+// keeps working with no internet access (same reasoning as the rest of this
+// route). Must be registered before the /:slug catch-all below, or "search"
+// would be treated as a (nonexistent) doc slug and 404.
+router.get('/search', function(req, res) {
+ const q = (req.query.q || '').trim();
+ if (!q) return res.json({results: []});
+ const qLower = q.toLowerCase();
+
+ const results = [];
+ for (const [slug, doc] of Object.entries(DOCS)) {
+ try {
+ const content = fs.readFileSync(doc.file, 'utf8');
+ const matchLine = content.split('\n').find(line => line.toLowerCase().includes(qLower));
+ if (matchLine) {
+ results.push({slug, title: doc.title, snippet: matchLine.trim().slice(0, 200)});
+ }
+ } catch (error) { /* unreadable doc file -- skip it */ }
+ }
+
+ res.json({results});
+});
+
router.get('/:slug', function(req, res, next) {
const doc = DOCS[req.params.slug];
if (!doc) return next({status: 404, message: 'Doc not found'});
diff --git a/nodejs/views/docs_index.ejs b/nodejs/views/docs_index.ejs
index 43c28d3..92bd78e 100644
--- a/nodejs/views/docs_index.ejs
+++ b/nodejs/views/docs_index.ejs
@@ -10,7 +10,12 @@
A local copy of this project's documentation, readable from the
running app -- no internet access required.
-