Implementation Plan: #4 — Implement Phase 1: Embedded Search Widget (Chatbot MCP)
Files
| # | Action | Path | Purpose |
|---|---|---|---|
| 1 | create | docs/_includes/head_custom.html | Injects chatbot CSS + JS into <head> via just-the-docs hook |
| 2 | create | docs/assets/css/chatbot.scss | Widget styles (brand tokens, animations, responsive) |
| 3 | create | docs/assets/js/chatbot.js | Widget logic (DOM, fetch, keyboard nav, state machine) |
Codebase Context
- just-the-docs theme auto-includes
_includes/head_custom.html— no config change needed - MCP response is double-encoded: parse
result.content[0].textas JSON to get{results, total} - Brand tokens in
docs/assets/css/custom.scss: primary#0267FF, border-radius8px, focus ringrgba(#0267FF, 0.12) - Fonts (Inter, Epilogue, Geist Mono) already loaded in
custom.scss— do NOT re-import inhead_custom.html - CORS is
*on/mcpedge function — cross-origin calls fromdocs.patttterns.comwork without config - Existing SCSS uses Jekyll front matter (
---header) to trigger Sass processing —chatbot.scssmust follow the same pattern docs/_includes/anddocs/assets/js/directories do not exist — must be created
Steps
-
Create
docs/_includes/head_custom.html— add<link>tochatbot.scssand<script defer>tochatbot.jsDone when: Jekyll build output includes the chatbot CSS and JS references in<head> -
Create
docs/assets/css/chatbot.scss— Jekyll front matter, brand-consistent styles for: trigger button, panel, input, result cards, empty state, loading spinner, error state, animations (slide-up) Done when: All widget visual states are styled and matchbrand.json/custom.scsstokens -
Create
docs/assets/js/chatbot.js— inject DOM (floating button + slide-up panel + input + results container), wire click/keyboard events, implement fetch to/mcp, parse double-encoded response, render results as cards Done when: Typing a query and pressing Enter renders result cards from the live/mcpendpoint -
Implement keyboard navigation — Escape closes panel, arrow keys navigate results with active index, Enter on focused result opens link Done when: Full search → navigate → select flow works without mouse
-
Manual QA — test on docs site: happy path, empty results, error state, keyboard-only flow, mobile viewport Done when: All 10 acceptance criteria pass in Chrome and Safari
Interfaces
interface McpResponse {
jsonrpc: string;
id: number;
result: {
content: Array<{ type: string; text: string }>;
};
}
interface PatternResult {
title: string;
description: string;
url: string;
type: string;
}
Function Design
| File | Function | Single Concern |
|---|---|---|
chatbot.js | createWidget() | Builds and injects DOM elements (button + panel + input + results container) |
chatbot.js | searchPatterns(query) | POST to /mcp, parse double-encoded response, return PatternResult[] |
chatbot.js | renderResults(results) | Clears container, creates result cards or empty/error state |
chatbot.js | setupKeyboardNav() | Escape close, Enter submit, arrow key navigation with active index tracking |
Acceptance Criteria (EARS)
- AC-1 (ubiquitous): The docs site shall display a floating button in the bottom-right corner using brand blue
#0267FF. - AC-2 (event-driven): When the user clicks the floating button, a 360px slide-up panel with a search input shall open.
- AC-3 (event-driven): When the user types a query and presses Enter, the widget shall POST to
/mcpwithsearch_patternsand the query. - AC-4 (event-driven): When results are returned, the widget shall render up to 5 clickable cards with title, description, and link.
- AC-5 (event-driven): When the user presses Escape, the panel shall close.
- AC-6 (event-driven): When the user presses arrow keys, the widget shall navigate between result items.
- AC-7 (event-driven): When no results are found or no query entered, the widget shall show: “Try searching for ‘onboarding’, ‘checkout’, or ‘navigation’”.
- AC-8 (ubiquitous): The widget shall use vanilla JS only — no framework dependencies.
- AC-9 (ubiquitous): The widget styles shall match the PATTTTERNS brand (colors, fonts, radius from
custom.scss). - AC-10 [inferred] (unwanted-behavior): If the MCP endpoint returns an error or is unreachable, the widget shall display an error message.
Out of Scope
- LLM / natural language mode (Phase 2)
- Embedding on
patttterns.commain site (Phase 3) - MCP
resources/readfor full pattern content (Phase 4) - Analytics or usage tracking on widget interactions
Edge Cases + Error Handling
| # | Scenario | Source | Handling |
|---|---|---|---|
| 1 | MCP endpoint unreachable / HTTP error | [inferred] | Show “Something went wrong. Try again.” with retry option |
| 2 | Malformed JSON / missing content[0].text | [inferred] | Catch parse error, show generic error message |
| 3 | Double-encoded response parsing | [inferred/codebase] | JSON.parse envelope, then JSON.parse inner text field |
| 4 | Empty results (0 matches) | [from issue] | Show empty state with suggested searches |
| 5 | Empty query submitted | [inferred] | Ignore submit, keep focus on input |
| 6 | Rapid sequential queries | [inferred] | AbortController — cancel previous fetch on new submit |
| 7 | Panel closed while fetch in-flight | [inferred] | Abort pending request on panel close |
| 8 | Multiple clicks on trigger button | [inferred] | Toggle open/close |
| 9 | Coexistence with JtD built-in search | [inferred] | Accept coexistence — separate UI, no conflict |
| 10 | Long description overflowing card | [inferred] | CSS line-clamp to 2 lines with ellipsis |
| 11 | Font double-loading | [inferred/codebase] | Fonts already in SCSS — do NOT add to head_custom.html |
Done Criteria per Feature
| Feature | Done when ACs pass |
|---|---|
| Trigger button | AC-1, AC-8, AC-9 |
| Panel open/close | AC-2, AC-5 |
| Search execution | AC-3, AC-8 |
| Results rendering | AC-4, AC-7, AC-9, AC-10 |
| Keyboard navigation | AC-5, AC-6 |
Risks
| # | Risk | Mitigation |
|---|---|---|
| 1 | /mcp endpoint latency from docs subdomain | Show loading spinner; AbortController timeout at 5s |
| 2 | Jekyll Sass processor may not find chatbot.scss | Use Jekyll front matter (---) at top, same pattern as custom.scss |
| 3 | JtD theme update could override head_custom.html behavior | head_custom.html is a documented stable hook in just-the-docs |
Test Strategy
- Manual (happy path): Open docs site locally (
bundle exec jekyll serve), search for “onboarding”, verify 5 result cards render with correct links - Network error: Simulate offline with DevTools network throttling, verify error state renders
- Keyboard-only: Tab to trigger button → Enter → type query → Enter → ArrowDown × 3 → Enter (opens link) → Escape (closes panel)
- Responsive: Test at 375px mobile viewport — panel should adapt width (full-width or max 360px)
- Cross-browser: Chrome + Safari minimum