Skip to content

Commit e8284bc

Browse files
authored
fix(docs): deploy bug report API as CF Worker + improve widget & layout (#154)
* feat(docs): add "Report a Bug" feedback widget to documentation site - Add BugReportWidget.vue: persistent side-tab with expandable form, success/error states, responsive design (side panel on desktop, bottom sheet on mobile), dark mode support, and prefers-reduced-motion - Add Cloudflare Pages Function (docs/functions/api/report-bug.ts): creates GitHub issues via GitHub App installation token with honeypot anti-spam, IP-based rate limiting via KV, input validation, and CORS restricted to docs domain - Add VitePress custom theme extending default with layout-bottom slot - Add .github/ISSUE_TEMPLATE/bug.yml as fallback for direct GitHub reports - Add @cloudflare/workers-types and docs/functions/tsconfig.json - Exclude docs/ from main tsconfig to avoid type conflicts Closes #145 * fix(docs): correct PEM detection, add focus trap and unit tests - Fix PKCS#1 vs PKCS#8 PEM detection: use "BEGIN RSA" presence to correctly identify PKCS#1 format (GitHub App PEMs) vs PKCS#8 - Add aria-modal="true" and focus trap to dialog panel for screen reader and keyboard-only accessibility - Return focus to trigger button when dialog closes - Extract pure utility functions to utils.ts for testability - Add 27 unit tests covering validation, CORS, base64url encoding, ASN.1 length encoding, and buffer concatenation * fix(docs): deduplicate constants and improve type narrowing Import CATEGORIES and MIN_DESCRIPTION_LENGTH from shared utils into the Vue widget instead of duplicating values. Add explicit local variable narrowing in validateReport for stricter TypeScript compatibility. * fix(docs): defer focus return to nextTick after widget collapse The trigger button uses v-if and is recreated on state change. Focus must wait for DOM patch before the new element ref is available. * fix(docs): reset honeypot field on form close to prevent stuck rejections * test(docs): add unit tests for report-bug CF handler with mocked crypto and fetch * fix(docs): allow close button to work in all non-collapsed widget states * fix(docs): deploy bug report API as standalone Cloudflare Worker GitHub Pages returns 405 for POST requests because it's static-only hosting. The CF Pages Function in docs/functions/ was never deployed. Solution: standalone Cloudflare Worker at docs/worker/ with route rule docs.gitlab-mcp.sw.foundation/api/* intercepting requests before GitHub Pages. - Add docs/worker/ with wrangler.toml and Worker-format handler - Add deploy-worker job to docs.yml CI workflow - Add https://docs.gitlab-mcp.sw.foundation to ALLOWED_ORIGINS - Update test expectation for new primary origin Closes #153 * feat(docs): improve widget visibility, add logo, widen layout - Widget: move from vertical side tab to floating bottom-right button with pulse animation and "Report Bug" label (more discoverable) - Logo: add project logo to navbar and hero section on landing page - Layout: add custom CSS with @media queries for wider content on screens >1440px and >1600px (reduces wasted horizontal space) * fix(docs): add KV namespace ID to wrangler.toml for rate limiting
1 parent b0b5308 commit e8284bc

File tree

14 files changed

+505
-36
lines changed

14 files changed

+505
-36
lines changed

.github/workflows/docs.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,15 @@ jobs:
6767
steps:
6868
- uses: actions/deploy-pages@v4
6969
id: deployment
70+
71+
deploy-worker:
72+
runs-on: ubuntu-latest
73+
steps:
74+
- uses: actions/checkout@v4
75+
76+
- name: Deploy Cloudflare Worker
77+
uses: cloudflare/wrangler-action@v3
78+
with:
79+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
80+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
81+
workingDirectory: docs/worker

docs/.vitepress/config.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export default defineConfig({
88
base,
99

1010
themeConfig: {
11+
logo: "/logo.png",
12+
1113
nav: [
1214
{ text: "Guide", link: "/guide/" },
1315
{ text: "Tools", link: "/tools/" },

docs/.vitepress/theme/components/BugReportWidget.vue

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ onUnmounted(() => {
165165
<path d="M22 13h-4" />
166166
<path d="M17.2 17c2.1.1 3.8 1.9 3.8 4" />
167167
</svg>
168-
<span class="bug-tab-text">Bug?</span>
168+
<span class="bug-tab-text">Report Bug</span>
169169
</button>
170170

171171
<!-- Expanded: form panel -->
@@ -299,70 +299,83 @@ onUnmounted(() => {
299299
<style scoped>
300300
.bug-report-widget {
301301
position: fixed;
302-
right: 0;
303-
top: 40%;
302+
right: 24px;
303+
bottom: 24px;
304304
z-index: 100;
305305
}
306306
307-
/* Collapsed side tab */
307+
/* Collapsed floating button */
308308
.bug-tab {
309309
display: flex;
310310
align-items: center;
311-
gap: 4px;
312-
padding: 8px 10px;
311+
gap: 6px;
312+
padding: 12px 16px;
313313
background: var(--vp-c-brand-1);
314314
color: #fff;
315315
border: none;
316-
border-radius: 8px 0 0 8px;
316+
border-radius: 24px;
317317
cursor: pointer;
318-
font-size: 13px;
319-
font-weight: 500;
320-
writing-mode: vertical-rl;
321-
text-orientation: mixed;
318+
font-size: 14px;
319+
font-weight: 600;
320+
writing-mode: horizontal-tb;
322321
transition:
323322
transform 0.2s ease,
324-
background 0.2s ease;
325-
box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.1);
323+
background 0.2s ease,
324+
box-shadow 0.2s ease;
325+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
326+
animation: pulse 3s ease-in-out 2s 3;
326327
}
327328
328329
.bug-tab:hover {
329-
transform: translateX(-2px);
330+
transform: translateY(-2px);
330331
background: var(--vp-c-brand-2);
332+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
333+
}
334+
335+
@keyframes pulse {
336+
0%,
337+
100% {
338+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
339+
}
340+
50% {
341+
box-shadow:
342+
0 4px 16px rgba(0, 0, 0, 0.2),
343+
0 0 0 6px rgba(var(--vp-c-brand-1-rgb, 66, 184, 131), 0.2);
344+
}
331345
}
332346
333347
.bug-tab svg {
334-
transform: rotate(90deg);
348+
transform: none;
335349
}
336350
337351
.bug-tab-text {
338-
margin-top: 4px;
352+
margin-top: 0;
339353
}
340354
341355
/* Expanded panel */
342356
.bug-panel {
343357
position: fixed;
344-
right: 16px;
345-
top: 50%;
346-
transform: translateY(-50%);
347-
width: 320px;
348-
max-height: 80vh;
358+
right: 24px;
359+
bottom: 80px;
360+
width: 340px;
361+
max-height: 70vh;
349362
overflow-y: auto;
350363
background: var(--vp-c-bg);
351364
border: 1px solid var(--vp-c-border);
352365
border-radius: 12px;
353-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
366+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
354367
padding: 16px;
355368
animation: slideIn 0.2s ease;
356369
}
357370
358371
@keyframes slideIn {
359372
from {
360373
opacity: 0;
361-
transform: translateY(-50%) translateX(20px);
374+
transform: translateY(12px);
362375
}
363376
to {
364377
opacity: 1;
365-
transform: translateY(-50%) translateX(0);
378+
transform: translateY(0);
366379
}
367380
}
368381
@@ -581,25 +594,19 @@ onUnmounted(() => {
581594
/* Mobile responsive */
582595
@media (max-width: 768px) {
583596
.bug-report-widget {
584-
right: auto;
585-
left: auto;
586-
top: auto;
587-
bottom: 16px;
588597
right: 16px;
598+
bottom: 16px;
589599
}
590600
591601
.bug-tab {
592-
writing-mode: horizontal-tb;
593602
border-radius: 50%;
594603
width: 48px;
595604
height: 48px;
596605
padding: 0;
597606
justify-content: center;
598-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
599607
}
600608
601609
.bug-tab svg {
602-
transform: none;
603610
width: 20px;
604611
height: 20px;
605612
}
@@ -609,14 +616,10 @@ onUnmounted(() => {
609616
}
610617
611618
.bug-panel {
612-
position: fixed;
613619
right: 8px;
614620
left: 8px;
615621
bottom: 8px;
616-
top: auto;
617622
width: auto;
618-
max-height: 70vh;
619-
transform: none;
620623
animation: slideUp 0.2s ease;
621624
}
622625

docs/.vitepress/theme/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { h } from "vue";
22
import DefaultTheme from "vitepress/theme";
33
import type { Theme } from "vitepress";
4+
import "./style.css";
45
import BugReportWidget from "./components/BugReportWidget.vue";
56

67
export default {

docs/.vitepress/theme/style.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Custom theme overrides for wider content layout on large screens.
3+
*/
4+
5+
/* Widen the doc content area on large screens */
6+
@media (min-width: 1440px) {
7+
.VPDoc .container {
8+
max-width: 1104px;
9+
}
10+
11+
.VPDoc:not(.has-aside) .container {
12+
max-width: 1104px;
13+
}
14+
15+
.VPDoc:not(.has-aside) .container > .content {
16+
max-width: 900px;
17+
}
18+
}
19+
20+
@media (min-width: 1600px) {
21+
.VPDoc .container {
22+
max-width: 1280px;
23+
}
24+
25+
.VPDoc:not(.has-aside) .container {
26+
max-width: 1280px;
27+
}
28+
29+
.VPDoc:not(.has-aside) .container > .content {
30+
max-width: 1040px;
31+
}
32+
}
33+
34+
/* Hero image sizing */
35+
.VPHero .image-container .image-src {
36+
max-width: 320px;
37+
max-height: 320px;
38+
}
39+
40+
@media (min-width: 960px) {
41+
.VPHero .image-container .image-src {
42+
max-width: 400px;
43+
max-height: 400px;
44+
}
45+
}

docs/functions/api/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface BugReport {
1212
}
1313

1414
export const ALLOWED_ORIGINS = [
15+
"https://docs.gitlab-mcp.sw.foundation",
1516
"https://structured-world.github.io",
1617
"http://localhost:5173",
1718
"http://localhost:4173",

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ hero:
55
name: GitLab MCP
66
text: Model Context Protocol Server
77
tagline: Connect AI agents to GitLab with 47 tools across 17 entity types
8+
image:
9+
src: /logo-hero.png
10+
alt: GitLab MCP Logo
811
actions:
912
- theme: brand
1013
text: Get Started

docs/public/logo-hero.png

474 KB
Loading

docs/public/logo.png

31.2 KB
Loading

docs/worker/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "gitlab-mcp-docs-api",
3+
"private": true,
4+
"type": "module",
5+
"devDependencies": {
6+
"@cloudflare/workers-types": "^4.20241218.0",
7+
"typescript": "^5.7.0",
8+
"wrangler": "^3.99.0"
9+
}
10+
}

0 commit comments

Comments
 (0)