Skip to content

Commit 2fb6d1f

Browse files
committed
feat: connect explain button to API
1 parent 6d038a8 commit 2fb6d1f

10 files changed

Lines changed: 688 additions & 494 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ yarn-error.log*
1515
pnpm-debug.log*
1616

1717
# environment variables
18-
.env
18+
.env*
1919
.env.production
2020

2121
# macOS-specific files

AGENTS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,20 @@ Tests use Vitest with three workspace projects (`vitest.workspace.ts`):
339339

340340
Run all tests: `npm run test`
341341

342+
## Web components
343+
344+
New web components in this codebase should use the `cfdocs-` prefix for custom element names (e.g., `<cfdocs-sheet>`, `<cfdocs-explain-code>`). This establishes a consistent naming pattern going forward.
345+
346+
### Naming conventions
347+
348+
- **Custom element names**: Use kebab-case with `cfdocs-` prefix (e.g., `cfdocs-sheet`)
349+
- **Class names**: Use PascalCase with `Element` suffix (e.g., `SheetElement`, `ExplainCodeElement`)
350+
- **File locations**: Place components in `src/components/{component-name}/` directories
351+
352+
### Existing components
353+
354+
Existing components (`warp-download`, `stream-player`, `rule-id`, `check-box`, `r2-local-uploads-diagram`, `animated-workflow-diagram`, `autoconfig-diagram`) are exempt from the `cfdocs-` prefix requirement and do not need to be renamed.
355+
342356
## Commit conventions
343357

344358
- Format: `[Product] description` or `type: description`
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { describe, expect, test } from "vitest";
2+
import { readFileSync } from "fs";
3+
import { join } from "path";
4+
5+
describe("ExplainCodeElement source", () => {
6+
const source = readFileSync(
7+
join(__dirname, "explain-code-sheet.ts"),
8+
"utf-8",
9+
);
10+
11+
test("imports sheet component", () => {
12+
expect(source).toContain('import "../sheet/sheet"');
13+
});
14+
15+
test("exports ExplainCodeElement class", () => {
16+
expect(source).toContain("class ExplainCodeElement extends HTMLElement");
17+
expect(source).toContain("export { ExplainCodeElement }");
18+
});
19+
20+
test("registers custom element with cfdocs-explain-code tag", () => {
21+
expect(source).toContain('customElements.define("cfdocs-explain-code"');
22+
});
23+
24+
test("implements connectedCallback", () => {
25+
expect(source).toContain("connectedCallback()");
26+
});
27+
28+
test("implements disconnectedCallback with abort", () => {
29+
expect(source).toContain("disconnectedCallback()");
30+
expect(source).toContain("this.abortController?.abort()");
31+
});
32+
33+
test("reads code-block-position attribute", () => {
34+
expect(source).toContain('getAttribute("code-block-position")');
35+
});
36+
37+
test("creates cfdocs-sheet element", () => {
38+
expect(source).toContain("<cfdocs-sheet></cfdocs-sheet>");
39+
});
40+
41+
test("listens for sheet-close event", () => {
42+
expect(source).toContain('addEventListener("sheet-close"');
43+
});
44+
45+
test("shows loading state initially", () => {
46+
expect(source).toContain("LOADING_HTML");
47+
expect(source).toContain("loading-skeleton");
48+
expect(source).toContain("skeleton-line");
49+
});
50+
51+
test("implements fetchExplanation method", () => {
52+
expect(source).toContain("async fetchExplanation()");
53+
});
54+
55+
test("uses AbortController for fetch cancellation", () => {
56+
expect(source).toContain("new AbortController()");
57+
expect(source).toContain("signal: this.abortController.signal");
58+
});
59+
60+
test("checks cf-docs-finish-reason header", () => {
61+
expect(source).toContain('headers.get("cf-docs-finish-reason")');
62+
expect(source).toContain('finishReason !== "stop"');
63+
});
64+
65+
test("handles AbortError silently", () => {
66+
expect(source).toContain('(error as Error).name === "AbortError"');
67+
expect(source).toContain("return;");
68+
});
69+
70+
test("shows error state on failure", () => {
71+
expect(source).toContain("ERROR_HTML");
72+
expect(source).toContain("error-state");
73+
});
74+
75+
test("has success HTML with explanation content", () => {
76+
expect(source).toContain("getSuccessHtml(explanation: string)");
77+
expect(source).toContain("explanation-content");
78+
});
79+
80+
test("includes AI provider buttons", () => {
81+
expect(source).toContain("Explore with ChatGPT");
82+
expect(source).toContain("Explore with Claude");
83+
expect(source).toContain('data-provider="chatgpt"');
84+
expect(source).toContain('data-provider="claude"');
85+
});
86+
87+
test("includes disclaimer", () => {
88+
expect(source).toContain("sheet-disclaimer");
89+
expect(source).toContain("experimental and may produce incorrect answers");
90+
});
91+
92+
test("uses PUBLIC_EXPLAIN_CODE_API_URL env var with fallback", () => {
93+
expect(source).toContain("PUBLIC_EXPLAIN_CODE_API_URL");
94+
expect(source).toContain("docs-ai-production.cloudflare-docs.workers.dev");
95+
});
96+
97+
test("builds correct API URL with path and codeBlock", () => {
98+
expect(source).toContain("window.location.pathname");
99+
expect(source).toContain("/explain/");
100+
expect(source).toContain("codeBlock=");
101+
});
102+
103+
test("injects styles with unique ID", () => {
104+
expect(source).toContain("cfdocs-explain-code-styles");
105+
expect(source).toContain('getElementById("cfdocs-explain-code-styles")');
106+
});
107+
108+
test("includes required CSS classes", () => {
109+
expect(source).toContain(".sheet-title");
110+
expect(source).toContain(".explanation-content");
111+
expect(source).toContain(".loading-skeleton");
112+
expect(source).toContain(".error-state");
113+
expect(source).toContain(".sheet-actions");
114+
expect(source).toContain(".explore-btn");
115+
expect(source).toContain(".sheet-disclaimer");
116+
});
117+
});

0 commit comments

Comments
 (0)