CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Jekyll 3.9 site for serin-labs.com — marketing, docs, blog, and storefront markup for the Serin CN105 smart controller for Mitsubishi mini-splits. Static output only: no server-side code lives here (the contact API and analytics run on the VPS, in the separate openbin-deploy repo).

Repos and deployment

The directory is named serin-labs.github.io but origin is the private repo Serin-Labs/serin-site. The public remote (Serin-Labs/serin-labs.github.io) is a CNAME-only 301 redirect stub that must stay public — never push site source to it.

Production is serin-labs.com, served by Caddy file_server on a VPS. .github/workflows/deploy-vps.yml runs on push to main: build → commerce tests → restricted rsync of _site/ → live smoke test of POST /api/contact (it fills the honeypot, so nothing is delivered). A broken contact endpoint fails the deploy.

redirect_to in _config.yml is a migration toggle: empty serves content normally; setting it turns every page into a redirect.

Commands

bundle is not on the default PATH on this machine:

export PATH="/home/akifb/.local/share/gem/ruby/3.3.0/bin:$PATH"
bundle exec jekyll build        # ~2s; a csv deprecation and a faraday-retry notice always print — not failures

Tests are plain Node scripts using assert — no runner, no package.json (and the repo must keep none; put jsdom harnesses and npm installs in the session scratchpad, never here). Run one with node tests/<name>.test.js.

The CI gate is: node tests/buy-link.test.js && node tests/check-build.js && node tests/render-prices.test.js && node tests/faq-match.test.js. CHECK_BUILD_STRICT=1 promotes check-build warnings to failures.

For browser verification, serve the built site over HTTP (python3 -m http.server -d _site) — the Playwright MCP Chrome blocks file:// URLs. Screenshots given a relative filename land in the repo working directory; delete them before committing.

Architecture

Drift-as-build-failure is the test philosophy. The same fact is deliberately stated in two places (data file + JS module, visible copy + JSON-LD), and a test makes disagreement fail the build:

Page JS (assets/js/) consists of small dependency-free modules wrapped in IIFEs that attach to window in a browser and module.exports under Node — that dual export is what lets tests require() them. Keep logic (decisions, classification, formatting) in these modules and out of inline render()/DOM code so it stays testable; flash-console.js is the reference example. Third-party JS is vendored in assets/js/vendor/ (never SRI-pin jsdelivr /+esm bundles — their hashes drift).

Blog: posts in _posts/, permalink /blog/:title.html. Jekyll's default future: false applies and there is no cron, so future-dated posts publish only when someone re-runs the deploy workflow on or after their date — this manual drip is deliberate. The backlog, evidence rules, and the house style contract (SmartHomeScene register) live in docs/content-backlog.md; read it before writing or editing a post.

Analytics: everything goes through window.serinTrack(name, props) defined in _layouts/default.html (guards a missing tracker, defers early calls). The Umami script loads first-party from /t/ — a Caddy proxy that exists only on serin-labs.com, so events are untestable on any other host. Event catalog: docs/analytics-events.md.

docs/, tests/, tools/, and the design_handoff_* dirs are excluded from the Jekyll build. temp/ holds full-res image masters (gitignored); tools/build-product-images.py derives the published assets/ images from them.

Content rules

Concurrent sessions

Multiple Claude sessions sometimes work this same worktree at once. "File modified since read" notices are usually real concurrent edits. Before staging a shared file, check git diff ownership and commit only your own hunks (rebuild HEAD + your edits via git hash-object -w + git update-index --cacheinfo if needed); re-run the relevant tests against what is actually staged, not just the worktree.