Skip to content

Commit 0f5153f

Browse files
docs: improve search result display and fix garbled titles (#3375)
* docs: improve search result display and fix garbled titles Strip tooltip text that the Algolia crawler injects into hierarchy values (e.g. "MoveMoveAn open source..." becomes "Move"). Replace the dual-link HitItem layout with a clean breadcrumb + title + snippet card. Widen the search modal to prevent tab label wrapping. Improve text contrast and add ESC key / click-outside-to-close. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add Kapa AI button to landing page and Clarity analytics Add "Ask Walrus AI" button to the landing page topbar that opens the Kapa modal. Add Microsoft Clarity tracking script for session analytics. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: fix search modal colors and scrolling Use existing walrus color variables for hit titles and breadcrumbs. Move critical flex/overflow layout to inline styles to ensure scroll container constrains properly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: restore original search modal layout, keep tooltip fix Revert the HitItem and modal container to match the live site's styling (single title link + snippet, max-w-3xl, Close button, original footer). Only change from the live version is that titles now use cleanTooltipText to strip garbled crawler tooltip text. ESC key and click-outside-to-close are retained. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent acaf90c commit 0f5153f

7 files changed

Lines changed: 152 additions & 70 deletions

File tree

docs/site/docusaurus.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ const config = {
272272

273273
scripts: [
274274
"/google-tag.js",
275+
"/clarity.js",
275276
{
276277
src: "https://widget.kapa.ai/kapa-widget.bundle.js",
277278
"data-website-id": "206d9923-4daf-4f2e-aeac-e7683daf5088",

docs/site/src/components/LandingPage.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ html, body { background: #FFFFFF !important; }
117117
.landing-root .topbar-links a.primary:hover {
118118
background: var(--purple-dim); border-color: var(--purple);
119119
}
120+
.landing-root .topbar-links .kapa-landing-btn {
121+
font-size: 1.3rem; padding: 6px 14px; border-radius: 8px;
122+
color: var(--purple); background: var(--purple-dim);
123+
border: 1px solid rgba(97,61,255,0.15); font-weight: 500;
124+
cursor: pointer; transition: all 0.2s; font-family: var(--sans);
125+
}
126+
.landing-root .topbar-links .kapa-landing-btn:hover {
127+
background: var(--purple-hover); border-color: var(--purple);
128+
}
120129
121130
/* ── Hero ── */
122131
.landing-root .hero {
@@ -465,6 +474,12 @@ export default function LandingPage() {
465474
>
466475
Discord
467476
</a>
477+
<button
478+
className="kapa-landing-btn"
479+
onClick={() => { if ((window as any).Kapa) (window as any).Kapa.open(); }}
480+
>
481+
Ask Walrus AI
482+
</button>
468483
<a href="/docs/getting-started" className="primary">
469484
Get Started →
470485
</a>

docs/site/src/components/Search/CustomHitsContent.tsx

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
import React from "react";
55
import { useHits } from "react-instantsearch";
66
import { useHistory } from "@docusaurus/router";
7-
import { truncateAtWord } from "./utils";
8-
9-
// Strips the crawler "__" format: "Page Title__Section__" → "Page Title"
10-
function cleanHierarchyValue(value: string | null | undefined): string {
11-
if (!value) return "";
12-
return value.split("__")[0].trim();
13-
}
7+
import {
8+
truncateAtWord,
9+
getDeepestHierarchyLabel,
10+
getHierarchyBreadcrumbs,
11+
cleanTooltipText,
12+
} from "./utils";
1413

1514
export default function CustomHitsContent({ name }) {
1615
const { hits: items } = useHits();
@@ -60,12 +59,11 @@ export default function CustomHitsContent({ name }) {
6059
return (
6160
<>
6261
{Object.entries(grouped).map(([key, group], index) => {
63-
// Clean group header — strips "__" crawler format from other indices
64-
const groupTitle =
65-
cleanHierarchyValue(group[0].hierarchy?.lvl1) ||
66-
cleanHierarchyValue(group[0].hierarchy?.lvl0) ||
67-
group[0].title ||
68-
"[no title]";
62+
const pageCrumbs = getHierarchyBreadcrumbs(group[0].hierarchy);
63+
const pageTitle =
64+
pageCrumbs.length > 0
65+
? pageCrumbs[Math.min(1, pageCrumbs.length - 1)]
66+
: "[no title]";
6967

7068
return (
7169
<div
@@ -74,54 +72,52 @@ export default function CustomHitsContent({ name }) {
7472
>
7573
{/* Group header: page title */}
7674
<div className="text-xl text-wal-purple-dark dark:text-wal-purple font-semibold mb-4">
77-
{groupTitle}
75+
{pageTitle}
7876
</div>
77+
{pageCrumbs.length > 0 && (
78+
<div className="text-xs text-[--color-walrus-dark-gray-300] dark:text-[--color-walrus-dark-gray-450] mb-4">
79+
{pageCrumbs.join(" > ")}
80+
</div>
81+
)}
7982

8083
<div>
8184
{group.map((hit, i) => {
82-
const level = hit.type;
83-
84-
// For content type, sectionTitle would duplicate the page title — skip it
85-
// For lvl1/lvl2/lvl3, show the specific heading
86-
const rawSectionTitle =
87-
level === "content" ? null : hit.hierarchy?.[level];
85+
const hitCrumbs = getHierarchyBreadcrumbs(hit.hierarchy);
8886
const sectionTitle =
89-
cleanHierarchyValue(rawSectionTitle) || null;
90-
91-
// Don't show sectionTitle if identical to group header
92-
const showSectionTitle =
93-
sectionTitle && sectionTitle !== groupTitle;
87+
hitCrumbs.length > 0
88+
? hitCrumbs[hitCrumbs.length - 1]
89+
: cleanTooltipText(
90+
getDeepestHierarchyLabel(hit.hierarchy),
91+
);
9492

9593
const hitHost = new URL(hit.url).host;
9694
const isInternal = hitHost === currentHost;
9795

9896
return (
9997
<div key={i} className="mb-2">
100-
{showSectionTitle && (
101-
isInternal ? (
102-
<button
103-
onClick={() =>
104-
history.push(new URL(hit.url).pathname)
105-
}
106-
className={
107-
"text-base text-blue-600 " +
108-
"hover:text-wal-green-dark underline " +
109-
"text-left bg-transparent border-0 pl-0 " +
110-
"cursor-pointer font-[Inter]"
111-
}
112-
>
113-
{sectionTitle}
114-
</button>
115-
) : (
116-
<a
117-
href={hit.url}
118-
target="_blank"
119-
rel="noopener noreferrer"
120-
className="text-base text-wal-link underline pb-2"
121-
>
122-
{sectionTitle}
123-
</a>
124-
)
98+
{isInternal ? (
99+
<button
100+
onClick={() =>
101+
history.push(new URL(hit.url).pathname)
102+
}
103+
className={
104+
"text-base text-blue-600 " +
105+
"hover:text-wal-green-dark underline " +
106+
"text-left bg-transparent border-0 pl-0 " +
107+
"cursor-pointer font-[Inter]"
108+
}
109+
>
110+
{sectionTitle}
111+
</button>
112+
) : (
113+
<a
114+
href={hit.url}
115+
target="_blank"
116+
rel="noopener noreferrer"
117+
className="text-base text-wal-link underline pb-2"
118+
>
119+
{sectionTitle}
120+
</a>
125121
)}
126122
<p
127123
className="font-normal text-base text-wal-gray-50 dark:text-wal-white-80"

docs/site/src/components/Search/SearchModal.tsx

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
useInstantSearch,
1010
Index,
1111
} from "react-instantsearch";
12-
import { truncateAtWord, getDeepestHierarchyLabel } from "./utils";
12+
import {
13+
truncateAtWord,
14+
getDeepestHierarchyLabel,
15+
cleanTooltipText,
16+
} from "./utils";
1317
import ControlledSearchBox from "./ControlledSearchBox";
1418
import TabbedResults from "./TabbedResults";
1519

@@ -72,9 +76,13 @@ function HitItem({ hit }: { hit: any }) {
7276
const level = hit.type;
7377
let sectionTitle = hit.lvl0;
7478
if (level === "content") {
75-
sectionTitle = getDeepestHierarchyLabel(hit.hierarchy);
79+
sectionTitle = cleanTooltipText(
80+
getDeepestHierarchyLabel(hit.hierarchy),
81+
);
7682
} else {
77-
sectionTitle = hit.hierarchy?.[level] || level;
83+
sectionTitle = cleanTooltipText(
84+
hit.hierarchy?.[level] || level,
85+
);
7886
}
7987

8088
const linkClasses =
@@ -83,14 +91,9 @@ function HitItem({ hit }: { hit: any }) {
8391

8492
return (
8593
<div className="modal-result">
86-
<a href={hit.url} className={`${linkClasses} font-medium`}>
87-
{hit.title}
88-
</a>
8994
<a
9095
href={hit.url}
91-
target="_blank"
92-
rel="noopener noreferrer"
93-
className={`text-base ${linkClasses} underline pb-2`}
96+
className={`${linkClasses} font-medium`}
9497
>
9598
{sectionTitle}
9699
</a>
@@ -215,6 +218,16 @@ export default function MultiIndexSearchModal({
215218
};
216219
}, [isOpen]);
217220

221+
useEffect(() => {
222+
if (!isOpen) return;
223+
const handleKeyDown = (e: KeyboardEvent) => {
224+
if (e.key === "Escape") onClose();
225+
};
226+
document.addEventListener("keydown", handleKeyDown);
227+
return () =>
228+
document.removeEventListener("keydown", handleKeyDown);
229+
}, [isOpen, onClose]);
230+
218231
const activeMeta = {
219232
walrus_docs: null,
220233
sui_docs: {
@@ -263,17 +276,27 @@ export default function MultiIndexSearchModal({
263276
<div
264277
className="fixed inset-0 z-500 flex justify-center p-4"
265278
style={{ backgroundColor: overlayBg }}
279+
onClick={(e) => {
280+
if (e.target === e.currentTarget) onClose();
281+
}}
266282
>
267283
<div
268284
className={
269285
"w-full max-w-3xl px-6 rounded-lg" +
270-
" shadow-md max-h-[600px] flex flex-col"
286+
" shadow-md flex flex-col"
271287
}
272-
style={{ backgroundColor: bg }}
288+
style={{
289+
backgroundColor: bg,
290+
maxHeight: "min(600px, 80vh)",
291+
}}
273292
>
274293
<div
275294
ref={scrollContainerRef}
276-
className="flex-1 overflow-y-auto"
295+
style={{
296+
flex: "1 1 0%",
297+
minHeight: 0,
298+
overflowY: "auto",
299+
}}
277300
>
278301
<InstantSearch
279302
searchClient={searchClient}
@@ -328,15 +351,20 @@ export default function MultiIndexSearchModal({
328351
key={index.indexName}
329352
>
330353
<ResultsUpdater
331-
indexName={index.indexName}
354+
indexName={
355+
index.indexName
356+
}
332357
onUpdate={(
333358
indexName,
334359
count,
335360
) =>
336-
setTabCounts((prev) => ({
337-
...prev,
338-
[indexName]: count,
339-
}))
361+
setTabCounts(
362+
(prev) => ({
363+
...prev,
364+
[indexName]:
365+
count,
366+
}),
367+
)
340368
}
341369
/>
342370
{index.indexName ===
@@ -348,7 +376,9 @@ export default function MultiIndexSearchModal({
348376
}
349377
/>
350378
<EmptyState
351-
label={index.label}
379+
label={
380+
index.label
381+
}
352382
/>
353383
</>
354384
)}

docs/site/src/components/Search/utils.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,35 @@ export function getDeepestHierarchyLabel(hierarchy) {
2424

2525
return lastValue || hierarchy.lvl6 || "";
2626
}
27+
28+
/**
29+
* Strip tooltip text injected by the Algolia crawler.
30+
* Pattern: a word is immediately followed by itself + a capital-letter
31+
* definition ending in a period.
32+
*/
33+
export function cleanTooltipText(text: string): string {
34+
// Remove zero-width spaces
35+
let cleaned = text.replace(/\u200B/g, "");
36+
// Strip duplicated-word tooltip definitions
37+
cleaned = cleaned.replace(/(\b\w{2,})\1[A-Z][^.]*\.\s?/g, "$1 ");
38+
return cleaned.trim();
39+
}
40+
41+
/**
42+
* Build an ordered breadcrumb array from a DocSearch hierarchy object.
43+
* Deduplicates adjacent identical levels. Strips crawler tooltip artefacts.
44+
*/
45+
export function getHierarchyBreadcrumbs(hierarchy): string[] {
46+
if (!hierarchy) return [];
47+
const levels = ["lvl0", "lvl1", "lvl2", "lvl3", "lvl4", "lvl5", "lvl6"];
48+
const crumbs: string[] = [];
49+
for (const lvl of levels) {
50+
const raw = hierarchy[lvl];
51+
if (raw == null) break;
52+
const value = cleanTooltipText(raw);
53+
if (crumbs.length === 0 || crumbs[crumbs.length - 1] !== value) {
54+
crumbs.push(value);
55+
}
56+
}
57+
return crumbs;
58+
}

docs/site/src/css/custom.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,11 +693,11 @@ font-weight: 700;
693693

694694
.sui-search mark,
695695
.modal-result mark {
696-
background-color: #e5e5e2;
696+
background-color: rgba(114, 83, 237, 0.12);
697697
}
698698
[data-theme="dark"] .sui-search mark,
699699
[data-theme="dark"] .modal-result mark {
700-
background-color: #53575a;
700+
background-color: rgba(114, 83, 237, 0.25);
701701
}
702702

703703
/* ══════════════════════════════════════════════

docs/site/static/clarity.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) Walrus Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
(function(c,l,a,r,i,t,y){
5+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
6+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
7+
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
8+
})(window, document, "clarity", "script", "wq0udz43r8");

0 commit comments

Comments
 (0)