Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<link rel="preconnect" href="https://us.i.posthog.com" crossorigin>
<link rel="preconnect" href="https://api.sassmaker.com" crossorigin>
<!-- fleet-jsonld:start (generated by apply-agent-surfaces; edit registry, not this) -->
<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"Person","@id":"https://sarthakagrawal.dev/#person","name":"Sarthak Agrawal","jobTitle":"AI Infrastructure & Product Engineer","url":"https://sarthakagrawal.dev","image":"https://avatars.githubusercontent.com/u/43884471?v=4","sameAs":["https://sarthakagrawal.dev","https://www.linkedin.com/in/sarthakagrawal927","https://github.com/sarthakagrawal927","https://x.com/sarthakcodes","https://huggingface.co/sarthakagrawal927"],"knowsAbout":["AI infrastructure","Local-first software","Post-training language models","AI code review","Cloudflare Workers","TypeScript","Rust"],"affiliation":{"@type":"Organization","@id":"https://sassmaker.com/#app","name":"SaaS Maker","url":"https://sassmaker.com"}},{"@type":"SoftwareApplication","@id":"https://chess.significanthobbies.com/#app","name":"Significant Hobbies","alternateName":["SignificantHobbies","significanthobbies.com"],"url":"https://chess.significanthobbies.com","description":"AI-coached chess game for practice with coaching feedback.","publisher":{"@id":"https://sarthakagrawal.dev/#person"},"sameAs":["https://github.com/Significant-Hobbies/chess"]}]}</script>
<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"Person","@id":"https://sarthakagrawal.dev/#person","name":"Sarthak Agrawal","jobTitle":"AI Infrastructure & Product Engineer","url":"https://sarthakagrawal.dev","image":"https://avatars.githubusercontent.com/u/43884471?v=4","sameAs":["https://sarthakagrawal.dev","https://www.linkedin.com/in/sarthakagrawal927","https://github.com/sarthakagrawal927","https://x.com/sarthakcodes","https://huggingface.co/sarthakagrawal927"],"knowsAbout":["AI infrastructure","Local-first software","Post-training language models","AI code review","Cloudflare Workers","TypeScript","Rust"],"affiliation":{"@type":"Organization","@id":"https://sassmaker.com/#app","name":"SaaS Maker","url":"https://sassmaker.com"}},{"@type":"WebSite","@id":"https://chess.significanthobbies.com/#app","name":"Chess Coach","url":"https://chess.significanthobbies.com","description":"AI-coached chess game for practice with coaching feedback.","publisher":{"@id":"https://sarthakagrawal.dev/#person"},"sameAs":["https://github.com/Significant-Hobbies/chess"]}]}</script>
<!-- fleet-jsonld:end -->
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-code-health.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const productionPaths = ['src', 'vite.config.ts']
const sourceExtensions = new Set(['.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'])

const baselines = {
complexity: { violations: 0, maxCcn: 20, maxLength: 100, maxParams: 7 },
complexity: { violations: 0, maxCcn: 14, maxLength: 65, maxParams: 7 },
duplication: { clones: 0, duplicatedLines: 0 },
unused: {
files: 0,
Expand Down
25 changes: 16 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,16 @@ function SiteHeader({ onOpenConfig }: { onOpenConfig?: () => void }) {
<span className="text-2xl">♟</span>
<div>
<span className="block text-lg font-bold text-gray-100 leading-none">Chess Coach</span>
<span className="block text-xs text-gray-500 mt-0.5">Play vs Stockfish with AI coaching</span>
<span className="block text-xs text-gray-500 mt-0.5">
Play vs Stockfish with AI coaching
</span>
</div>
</a>
<div className="flex items-center gap-3 sm:gap-4">
<nav className="flex items-center gap-3 text-sm" aria-label="Site">
<a href="/faq" className="text-gray-400 hover:text-gray-200 transition-colors">FAQ</a>
<a href="/faq" className="text-gray-400 hover:text-gray-200 transition-colors">
FAQ
</a>
<a href="/changelog" className="text-gray-400 hover:text-gray-200 transition-colors">
Changelog
</a>
Expand Down Expand Up @@ -82,8 +86,12 @@ function Changelog() {
Meaningful improvements to browser-based chess practice, reliability, and learning.
</p>
<nav className="mt-6 flex flex-wrap gap-5 text-sm" aria-label="Project links">
<a className="text-amber-300 hover:text-amber-200" href={`${REPOSITORY}/issues`}>Roadmap</a>
<a className="text-amber-300 hover:text-amber-200" href={REPOSITORY}>Source</a>
<a className="text-amber-300 hover:text-amber-200" href={`${REPOSITORY}/issues`}>
Roadmap
</a>
<a className="text-amber-300 hover:text-amber-200" href={REPOSITORY}>
Source
</a>
</nav>
</header>
<ol className="mt-12 space-y-5">
Expand All @@ -99,7 +107,9 @@ function Changelog() {
</time>
<h2 className="mt-2 text-xl font-semibold">{release.title}</h2>
<ul className="mt-3 list-disc space-y-2 pl-5 leading-7 text-gray-400">
{release.outcomes.map((outcome) => <li key={outcome}>{outcome}</li>)}
{release.outcomes.map((outcome) => (
<li key={outcome}>{outcome}</li>
))}
</ul>
</article>
</li>
Expand All @@ -123,10 +133,7 @@ export default function App() {
setAIConfig(config)
}, [])

if (
window.location.pathname === '/changelog' ||
window.location.pathname === '/changelog.html'
) {
if (window.location.pathname === '/changelog' || window.location.pathname === '/changelog.html') {
return <Changelog />
}

Expand Down
17 changes: 9 additions & 8 deletions src/components/AIConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ export function AIConfigModal({ onClose, onSave }: AIConfigModalProps) {
<div className="bg-gray-900 border border-gray-700 rounded-xl w-full max-w-md shadow-2xl">
<div className="flex items-center justify-between p-4 border-b border-gray-700">
<h2 className="text-lg font-semibold text-gray-100">AI Coach Settings</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-200 transition-colors"
>
<button onClick={onClose} className="text-gray-400 hover:text-gray-200 transition-colors">
<X className="w-5 h-5" />
</button>
</div>
Expand All @@ -60,7 +57,9 @@ export function AIConfigModal({ onClose, onSave }: AIConfigModalProps) {
className="bg-gray-800 border border-gray-600 text-gray-100 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-blue-500"
>
{PROVIDERS.map((p) => (
<option key={p.value} value={p.value}>{p.label}</option>
<option key={p.value} value={p.value}>
{p.label}
</option>
))}
</select>
</div>
Expand All @@ -73,14 +72,16 @@ export function AIConfigModal({ onClose, onSave }: AIConfigModalProps) {
className="bg-gray-800 border border-gray-600 text-gray-100 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-blue-500"
>
{models.map((m) => (
<option key={m} value={m}>{m}</option>
<option key={m} value={m}>
{m}
</option>
))}
</select>
</div>

<div className="bg-blue-900/30 border border-blue-700 rounded-lg p-3 text-sm text-blue-300">
Local AI providers do not need an API key. Run the local bridge and
make sure the selected CLI is installed and authenticated.
Local AI providers do not need an API key. Run the local bridge and make sure the
selected CLI is installed and authenticated.
</div>

<button
Expand Down
18 changes: 11 additions & 7 deletions src/components/ChessClock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
interface ChessClockProps {
time: number // seconds remaining
isActive: boolean // whose turn it is
label: string // 'You' | 'Computer'
time: number // seconds remaining
isActive: boolean // whose turn it is
label: string // 'You' | 'Computer'
}

export function ChessClock({ time, isActive, label }: ChessClockProps) {
Expand All @@ -11,14 +11,18 @@ export function ChessClock({ time, isActive, label }: ChessClockProps) {
const isLow = time <= 30 && time > 0

return (
<div className={`
<div
className={`
flex items-center justify-between px-4 py-2 rounded-lg transition-colors duration-300
${isActive ? 'bg-gray-100 text-gray-900' : 'bg-gray-800/80 text-gray-500'}
`}>
`}
>
<span className="text-sm font-medium">{label}</span>
<span className={`font-mono text-2xl font-bold tabular-nums tracking-tight
<span
className={`font-mono text-2xl font-bold tabular-nums tracking-tight
${isLow && isActive ? 'text-red-500' : ''}
`}>
`}
>
{formatted}
</span>
</div>
Expand Down
Loading