Fix browser script serving: run build after skill sync, serve via /js/

The skill sync wipes .claude/skills/ and re-copies from dist, deleting
the generated browser script. Moved build-browser-detector.js to run
AFTER the sync. Dev server's /js/* route now falls through to
.claude/skills/critique/scripts/ for built artifacts. All fixture HTML
references use /js/detect-antipatterns-browser.js (clean URL).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-17 18:26:51 -07:00
co-authored by Claude Opus 4.6
parent fbfe525f4f
commit d8803c8151
25 changed files with 44 additions and 51 deletions
@@ -202,6 +202,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -370,6 +370,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -312,6 +312,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -317,6 +317,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -315,6 +315,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -373,6 +373,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -194,6 +194,6 @@
<script>
lucide.createIcons();
</script>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -431,6 +431,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -236,6 +236,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -202,6 +202,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
@@ -200,6 +200,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+7 -7
View File
@@ -322,13 +322,6 @@ async function build() {
// Build CSS with Tailwind CLI (handles @theme directive)
buildTailwindCSS();
// Generate browser anti-pattern detector from core module
try {
execSync('node scripts/build-browser-detector.js', { cwd: ROOT_DIR, stdio: 'inherit' });
} catch (error) {
console.error('Failed to build browser detector:', error.message);
}
// Bundle HTML, JS, and compiled CSS with Bun
await buildStaticSite();
@@ -395,6 +388,13 @@ async function build() {
console.log(`📋 Synced to .claude/: skills`);
// Generate browser anti-pattern detector (after skill sync so it doesn't get overwritten)
try {
execSync('node scripts/build-browser-detector.js', { cwd: ROOT_DIR, stdio: 'inherit' });
} catch (error) {
console.error('Failed to build browser detector:', error.message);
}
console.log('\n✨ Build complete!');
}
+8 -20
View File
@@ -19,19 +19,6 @@ const server = serve({
"/cheatsheet": cheatsheet,
"/gallery": gallery,
// Built skill scripts (.claude/skills/)
"/.claude/skills/*": async (req) => {
const url = new URL(req.url);
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
const filePath = `.${url.pathname}`;
const assetFile = file(filePath);
if (await assetFile.exists()) {
return new Response(assetFile, {
headers: { "Content-Type": "application/javascript", "X-Content-Type-Options": "nosniff" }
});
}
return new Response("Not Found", { status: 404 });
},
// Static assets - all public subdirectories
"/assets/*": async (req) => {
const url = new URL(req.url);
@@ -60,13 +47,14 @@ const server = serve({
"/js/*": async (req) => {
const url = new URL(req.url);
if (url.pathname.includes('..')) return new Response("Bad Request", { status: 400 });
const filePath = `./public${url.pathname}`;
const assetFile = file(filePath);
if (await assetFile.exists()) {
return new Response(assetFile, {
headers: { "Content-Type": "application/javascript", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY" }
});
}
// Check public/js/ first, then fall back to built artifacts
const headers = { "Content-Type": "application/javascript", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY" };
const publicFile = file(`./public${url.pathname}`);
if (await publicFile.exists()) return new Response(publicFile, { headers });
// Built browser detector served at /js/detect-antipatterns-browser.js
const basename = url.pathname.split('/').pop();
const builtFile = file(`./.claude/skills/critique/scripts/${basename}`);
if (await builtFile.exists()) return new Response(builtFile, { headers });
return new Response("Not Found", { status: 404 });
},
// Test fixtures (for browser visual testing)
+7 -2
View File
@@ -38,8 +38,13 @@ describeIf('browser script parity with CLI', () => {
let filePath;
if (req.url.startsWith('/fixtures/')) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir))}, req.url);
} else if (req.url.startsWith('/.claude/')) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..'))}, req.url);
} else if (req.url.startsWith('/js/')) {
// Check public/js/ first, then built artifacts
const basename = req.url.split('/').pop();
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..', 'public'))}, req.url);
if (!fs.existsSync(filePath)) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..', '.claude', 'skills', 'critique', 'scripts'))}, basename);
}
} else {
res.writeHead(404); res.end(); return;
}
+1 -1
View File
@@ -75,6 +75,6 @@
<p>Purple-to-indigo gradient</p>
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -44,6 +44,6 @@
<h3 style="color: rgb(220, 38, 38); font-size: 1.5rem;">Red heading — not AI purple</h3>
<h3 style="color: rgb(180, 83, 9); font-size: 1.5rem;">Amber heading — distinctive</h3>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -132,6 +132,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -237,6 +237,6 @@
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -108,6 +108,6 @@
<strong>Warning:</strong> Your trial expires in 3 days. <a href="#" style="color: #d97706;">Upgrade now</a>
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -43,6 +43,6 @@
<p style="font-size: 0.875rem; color: #6b7280;">Uniform 1px border — should NOT flag.</p>
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -8,4 +8,4 @@
<p style="font-size: 15px; color: #6b7280;">Card description with close font sizes.</p>
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
+1 -1
View File
@@ -131,6 +131,6 @@
<p style="font-size: 0.8125rem; color: #9ca3af;">Inline dark card with side-tab.</p>
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -79,6 +79,6 @@
<p style="font-size: 0.875rem; color: #94a3b8; margin-top: 0.25rem;">Shadow only. Clean.</p>
</div>
</div>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -34,6 +34,6 @@
<h3>A Subheading</h3>
<p>Can you tell this is a subheading? Exactly.</p>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+1 -1
View File
@@ -40,6 +40,6 @@
<h3>Strong Size Hierarchy</h3>
<p>Sizes range from 12px to 48px — a 4:1 ratio with clear visual steps.</p>
<p class="caption">Caption text is clearly distinct from body.</p>
<script src="/.claude/skills/critique/scripts/detect-antipatterns-browser.js"></script>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>