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
index 44489ea..fb6c75c 100644
--- 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
@@ -59,6 +59,13 @@ NodeBB version:
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
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 @@
+
[[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 }}}
+
+{{{ 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 }}}
+
+ {{{each categories}}}
+
+ {{{end}}}
+
+ {{{ 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";