diff --git a/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md b/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md new file mode 100644 index 0000000..fb6c75c --- /dev/null +++ b/docs/superpowers/specs/2026-07-02-wiki-search-result-badge-design.md @@ -0,0 +1,95 @@ +# Wiki Badge in Global Search Results — Theme Design + +Date: 2026-07-02 +Status: Approved for planning +Scope: theme-only slice of the cross-repo "wiki in global search" work. + +Companion spec (drives this): `sow-nodebb-plugin-wiki` → +`docs/superpowers/specs/2026-07-02-wiki-global-search-design.md`. The wiki +plugin makes wiki articles searchable and tags each wiki result with +`isWikiArticle: true` and `wikiPath` (`/wiki/...`). **This spec covers only the +rendering of those tagged results in the theme.** + +## Problem + +The theme currently ships no search-result overrides — global search +(quick-search dropdown + `/search` page) renders with stock NodeBB templates. +Once the wiki plugin surfaces wiki articles in search, those results need to be +visually marked as wiki and to link to `/wiki/...` instead of `/topic/...`. + +## Input contract (from the wiki plugin) + +Each search result object may carry: + +- `isWikiArticle` — `true` for wiki article hits. +- `wikiPath` — the `/wiki/...` URL for the article. + +Forum results do not carry these; they render unchanged. + +## Design + +Mark wiki result rows and point them at the wiki URL, in **both** surfaces: + +1. **`/search` page** — the full results list. +2. **Quick-search dropdown** — the navbar type-ahead results. + +For each row where `isWikiArticle` is set: + +- show a wiki icon/badge (e.g. a book/wiki icon plus a small "Wiki" label), + consistent with existing theme iconography (see `GAME_ICONS.md`); +- set the result's link `href` to `wikiPath`. + +Rows stay in the single relevance-ranked list returned by search; no grouping or +reordering. + +### Approach — decide during planning + +Two ways to inject this; pick the smaller one that works against the pinned +NodeBB version: + +- **Template override (preferred if a small partial isolates the row):** add a + theme override of the NodeBB search result partial(s) — the quick-search + results partial and the `/search` results partial — with a conditional badge + and `wikiPath` link. Keeps logic declarative and server-rendered. +- **Client-side decoration (fallback):** in `public/client.js`, after results + render, decorate rows flagged `isWikiArticle` (badge + rewrite href). Smaller + surface if the stock partials are awkward to override cleanly, but relies on + the data being present in the client payload. + +Planning resolves which, based on the actual stock partials in the pinned +NodeBB/theme base. + +**Resolved (2026-07-03): template override.** Harmony's row markup is small and +self-contained, so the theme overrides `partials/posts_list_item.tpl` (`/search` +posts view — shared with account/groups pages, where the wiki flags are never +set and the conditional no-ops), `partials/search-results.tpl` (`/search` topics +view), and `partials/quick-search-results.tpl` (dropdown). No client-side +decoration needed. + +## Styling + +Add badge styles in the theme SCSS (`scss/`, wired via `theme.scss`), matching +existing badge/pill conventions. No new dependencies. + +## Non-goals + +- No search *behavior* changes — indexing, matching, ranking, and result tagging + all live in the wiki plugin. +- No grouping/separate section — inline badge only. +- No changes to non-search surfaces. + +## Testing / verification + +- Visual: run the dev stack (`docker-compose.dev.yml` in `sow-nodebb`), search a + term matching a wiki article, confirm the result shows the wiki badge and its + link goes to `/wiki/...`; confirm forum results are unchanged. Check both the + dropdown and the `/search` page. +- Snapshot/DOM assertions only if the theme's existing test setup (`tests/`) + makes them cheap; otherwise rely on the visual check (badge/link is not + complex logic). + +## Dependencies / sequencing + +Depends on the wiki plugin emitting `isWikiArticle` + `wikiPath` on results. +Theme work can be built against a stubbed result but is only end-to-end +verifiable once the plugin side lands. diff --git a/languages/en-GB/westgate.json b/languages/en-GB/westgate.json new file mode 100644 index 0000000..6b3e767 --- /dev/null +++ b/languages/en-GB/westgate.json @@ -0,0 +1,3 @@ +{ + "wiki-badge": "Wiki" +} diff --git a/scss/westgate/_search.scss b/scss/westgate/_search.scss new file mode 100644 index 0000000..ba19512 --- /dev/null +++ b/scss/westgate/_search.scss @@ -0,0 +1,17 @@ +/* Wiki badge on global search results (quick-search dropdown + /search page). + Marks results tagged by nodebb-plugin-westgate-wiki (isWikiArticle/wikiPath). */ +.westgate-wiki-badge { + font-family: var(--wg-font-ui); + font-size: 0.62em; + font-weight: 600; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--wg-gold); + background: rgba(194, 163, 90, 0.1); + border: 1px solid var(--wg-ledger-border); + vertical-align: middle; +} + +.quick-search-results .westgate-wiki-badge { + font-size: 0.7rem; +} diff --git a/templates/partials/posts_list_item.tpl b/templates/partials/posts_list_item.tpl new file mode 100644 index 0000000..9637d78 --- /dev/null +++ b/templates/partials/posts_list_item.tpl @@ -0,0 +1,28 @@ +
  • +
    + + {{{ if ./isWikiArticle }}} [[westgate:wiki-badge]] {{{ else }}}{{{ if ./isMainPost }}} {{{ end }}}{{{ end }}}{./topic.title} + + +
    + + +
    + {./content} +
    +
    +
    + {buildCategoryLabel(./category, "a", "border")} + + {{{ each ./topic.tags }}} + {./valueEscaped} + {{{ end }}} + +
    +
  • \ No newline at end of file diff --git a/templates/partials/quick-search-results.tpl b/templates/partials/quick-search-results.tpl new file mode 100644 index 0000000..3e60396 --- /dev/null +++ b/templates/partials/quick-search-results.tpl @@ -0,0 +1,50 @@ +{{{ if (multiplePages && !config.theme.topMobilebar) }}} +
    +
    + [[search:see-more-results, {matchCount}]] +
    + +
    +{{{ end }}} + + + +{{{ if multiplePages }}} +
    + [[search:see-more-results, {matchCount}]] +
    +{{{ end }}} +{{{ if (multiplePages && config.theme.topMobilebar) }}} +
    + [[search:see-more-results, {matchCount}]] +
    +{{{ end }}} + +{{{if !posts.length}}} +
    [[search:no-matches]] + +{{{end}}} \ No newline at end of file diff --git a/templates/partials/search-results.tpl b/templates/partials/search-results.tpl new file mode 100644 index 0000000..fd5327b --- /dev/null +++ b/templates/partials/search-results.tpl @@ -0,0 +1,55 @@ +{{{ if matchCount }}} +
    [[search:results-matching, {matchCount}, {search_query}, {time}]]
    +{{{ else }}} +{{{ if search_query }}} +
    [[search:no-matches]]
    +{{{ end }}} +{{{ end }}} + +
    + {{{ if showAsPosts }}} + {{{ if posts.length }}} + + {{{ end }}} + {{{ end }}} + + {{{ if showAsTopics }}} + {{{ each posts }}} +
    + + {{{ end }}} + {{{ end }}} + + {{{ if users.length }}} + + {{{ end }}} + + {{{ if tags.length }}} +
    + +
    + {{{ end }}} + + {{{ if categories.length }}} + + {{{ end }}} + + +
    \ No newline at end of file diff --git a/tests/wiki-search-badge-contract.test.js b/tests/wiki-search-badge-contract.test.js new file mode 100644 index 0000000..c998292 --- /dev/null +++ b/tests/wiki-search-badge-contract.test.js @@ -0,0 +1,42 @@ +'use strict'; + +// Contract: search results tagged by nodebb-plugin-westgate-wiki +// (isWikiArticle + wikiPath) render with a wiki badge and link to /wiki/..., +// while untagged forum results keep the stock /post/... link. + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +function read(relativePath) { + const filePath = path.join(__dirname, '..', relativePath); + assert(fs.existsSync(filePath), `${relativePath} should exist`); + return fs.readFileSync(filePath, 'utf8'); +} + +function assertIncludes(haystack, needle, message) { + assert( + haystack.includes(needle), + message || `Expected file to include ${needle}` + ); +} + +[ + ['templates/partials/posts_list_item.tpl', './isWikiArticle', '{./wikiPath}', '/post/{encodeURIComponent(./pid)}'], + ['templates/partials/search-results.tpl', './isWikiArticle', '{./wikiPath}', '/post/{encodeURIComponent(./pid)}'], + ['templates/partials/quick-search-results.tpl', 'posts.isWikiArticle', '{posts.wikiPath}', '/post/{encodeURIComponent(posts.pid)}'], +].forEach(([templatePath, flag, wikiHref, forumHref]) => { + const template = read(templatePath); + assertIncludes(template, flag, `${templatePath} should branch on the wiki result flag`); + assertIncludes(template, wikiHref, `${templatePath} should link wiki results to their wikiPath`); + assertIncludes(template, forumHref, `${templatePath} should keep the stock post link for forum results`); + assertIncludes(template, 'westgate-wiki-badge', `${templatePath} should render the wiki badge`); +}); + +assertIncludes(read('theme.scss'), 'westgate/search', 'theme.scss should import the search styles'); +assertIncludes(read('scss/westgate/_search.scss'), '.westgate-wiki-badge', 'search styles should define the wiki badge'); + +const languageKeys = JSON.parse(read('languages/en-GB/westgate.json')); +assert(languageKeys['wiki-badge'], 'westgate language file should define the wiki-badge label'); + +console.log('wiki search badge contract tests passed'); diff --git a/theme.scss b/theme.scss index ed6fbfc..39ae4b7 100644 --- a/theme.scss +++ b/theme.scss @@ -7,6 +7,7 @@ @import "./scss/westgate/categories"; @import "./scss/westgate/topics"; @import "./scss/westgate/posts"; +@import "./scss/westgate/search"; @import "./scss/westgate/controls"; @import "./scss/westgate/widgets"; @import "./scss/westgate/responsive";