Compare commits

..

No commits in common. "5f5e0da3c486eedafca4d6616e77399f0a912f10" and "fe6d20afbdecaf3a5206a30812f3d0d027baa0a4" have entirely different histories.

7 changed files with 7 additions and 297 deletions

View File

@ -47,8 +47,6 @@ class NotebookSummary(BaseModel):
tags: list[str]
cell_count: int
modified: str
description: str | None = None
schematic_svg: str | None = None
class CreateNotebookRequest(BaseModel):

View File

@ -51,44 +51,6 @@ def _notebook_id_from_path(path: Path) -> str:
return path.stem
def _extract_card_data(nb: Notebook) -> tuple[str | None, str | None]:
"""Extract a description snippet and schematic SVG from notebook cells.
- description: text from the first markdown cell, with the title header
stripped and truncated to ~200 characters.
- schematic_svg: SVG string from the first successful schematic output.
"""
description: str | None = None
schematic_svg: str | None = None
for cell in nb.cells:
if description is None and cell.type == CellType.MARKDOWN and cell.source.strip():
lines = cell.source.strip().splitlines()
# Strip leading "# Title" header line
if lines and lines[0].lstrip().startswith("#"):
lines = lines[1:]
text = " ".join(line.strip() for line in lines if line.strip())
# Strip basic markdown syntax
text = re.sub(r"[*_`~\[\]#>]", "", text).strip()
if text:
description = text[:200].rstrip()
if schematic_svg is None:
for output in cell.outputs:
if (
output.output_type == "schematic"
and output.data.get("success")
and output.data.get("svg")
):
schematic_svg = output.data["svg"]
break
if description is not None and schematic_svg is not None:
break
return description, schematic_svg
def list_notebooks(directory: Path) -> list[NotebookSummary]:
"""List all notebooks in the directory and its examples/ subdirectory."""
summaries: list[NotebookSummary] = []
@ -104,7 +66,6 @@ def list_notebooks(directory: Path) -> list[NotebookSummary]:
try:
nb = _read_notebook_file(path)
description, schematic_svg = _extract_card_data(nb)
summaries.append(NotebookSummary(
id=nb_id,
title=nb.metadata.title,
@ -112,8 +73,6 @@ def list_notebooks(directory: Path) -> list[NotebookSummary]:
tags=nb.metadata.tags,
cell_count=len(nb.cells),
modified=nb.metadata.modified,
description=description,
schematic_svg=schematic_svg,
))
except Exception:
logger.warning("Skipping corrupt notebook: %s", path)

View File

@ -4,50 +4,23 @@ services:
context: ./backend
dockerfile: Dockerfile
target: dev
expose:
- "8000"
ports:
- "${BACKEND_PORT:-8099}:8000"
volumes:
- ./backend/src:/app/src
- ./notebooks:/app/notebooks
# Mount mcltspice for live development
- ../mcp-ltspice/src/mcltspice:/app/mcltspice-lib:ro
environment:
- PYTHONPATH=/app/src
command: ["python", "-m", "uvicorn", "spicebook.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks:
- default
- caddy
labels:
caddy: "${SPICEBOOK_DOMAIN:-spicebook.localhost}"
caddy.@api.path: "/api/* /health /docs /openapi.json /redoc"
caddy.reverse_proxy_0: "@api {{upstreams 8000}}"
command: ["uv", "run", "uvicorn", "spicebook.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: dev
expose:
- "4321"
ports:
- "${FRONTEND_PORT:-4321}:4321"
volumes:
- ./frontend/src:/app/src
- ./frontend/public:/app/public
command: ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
networks:
- default
- caddy
labels:
caddy: "${SPICEBOOK_DOMAIN:-spicebook.localhost}"
caddy.reverse_proxy_1: "{{upstreams 4321}}"
caddy.reverse_proxy_1.flush_interval: "-1"
caddy.reverse_proxy_1.transport: "http"
caddy.reverse_proxy_1.transport.read_timeout: "0"
caddy.reverse_proxy_1.transport.write_timeout: "0"
caddy.reverse_proxy_1.transport.keepalive: "5m"
caddy.reverse_proxy_1.transport.keepalive_idle_conns: "10"
caddy.reverse_proxy_1.stream_timeout: "24h"
caddy.reverse_proxy_1.stream_close_delay: "5s"
networks:
caddy:
external: true

View File

@ -25,21 +25,14 @@ interface NotebookCardProps {
}
export function NotebookCard({ notebook }: NotebookCardProps) {
const { id, title, engine, tags, cell_count, modified, description, schematic_svg } = notebook;
const { id, title, engine, tags, cell_count, modified } = notebook;
return (
<a
href={`/notebook/${encodeURIComponent(id)}`}
className="group block rounded-lg border border-slate-800 bg-slate-900/50 overflow-hidden hover:border-blue-500/40 hover:bg-slate-800/70 transition-all"
>
{schematic_svg ? (
<div
className="w-full h-[120px] bg-[#f8faf6] border-b border-slate-800/60 overflow-hidden flex items-center justify-center [&_svg]:max-w-full [&_svg]:max-h-full [&_svg]:w-auto [&_svg]:h-auto"
dangerouslySetInnerHTML={{ __html: schematic_svg }}
/>
) : (
<div className="notebook-card-strip" data-engine={engine} />
)}
<div className="notebook-card-strip" data-engine={engine} />
<div className="p-5">
<div className="flex items-start justify-between mb-2">
<h3 className="text-base font-semibold text-slate-100 group-hover:text-blue-400 transition-colors line-clamp-1">
@ -50,12 +43,6 @@ export function NotebookCard({ notebook }: NotebookCardProps) {
</Badge>
</div>
{description && (
<p className="text-sm text-slate-400 leading-relaxed mb-2 line-clamp-2">
{description}
</p>
)}
<div className="flex items-center gap-3 text-xs text-slate-500 mb-3">
<span className="inline-flex items-center gap-1">
<LayoutGrid className="w-3 h-3" />

View File

@ -1,199 +0,0 @@
---
/**
* Resistor Color Code Guide — educational reference section.
*
* Left column: annotated SVG showing a 10k resistor zigzag with labeled bands.
* Right column: compact reference table mapping colors to digits and multipliers.
*
* Colors match BAND_HEX from resistor_colors.py so dots match schematic rendering.
*/
// BAND_HEX values from backend/src/spicebook/engine/resistor_colors.py
const bandHex: Record<string, string> = {
black: '#1a1a1a',
brown: '#8B4513',
red: '#CC0000',
orange: '#E67300',
yellow: '#B8960B',
green: '#006400',
blue: '#0000CC',
violet: '#7B00B3',
gray: '#666666',
white: '#D0D0D0',
gold: '#B8860B',
silver: '#808080',
};
const digitBands = [
{ color: 'Black', hex: bandHex.black, digit: 0, multiplier: '×1 \u03A9' },
{ color: 'Brown', hex: bandHex.brown, digit: 1, multiplier: '×10 \u03A9' },
{ color: 'Red', hex: bandHex.red, digit: 2, multiplier: '×100 \u03A9' },
{ color: 'Orange', hex: bandHex.orange, digit: 3, multiplier: '×1 k\u03A9' },
{ color: 'Yellow', hex: bandHex.yellow, digit: 4, multiplier: '×10 k\u03A9' },
{ color: 'Green', hex: bandHex.green, digit: 5, multiplier: '×100 k\u03A9' },
{ color: 'Blue', hex: bandHex.blue, digit: 6, multiplier: '×1 M\u03A9' },
{ color: 'Violet', hex: bandHex.violet, digit: 7, multiplier: '×10 M\u03A9' },
{ color: 'Gray', hex: bandHex.gray, digit: 8, multiplier: '\u2014' },
{ color: 'White', hex: bandHex.white, digit: 9, multiplier: '\u2014' },
];
const toleranceBands = [
{ color: 'Gold', hex: bandHex.gold, value: '\u00B15%' },
{ color: 'Silver', hex: bandHex.silver, value: '\u00B110%' },
];
---
<section class="max-w-6xl mx-auto px-6 py-16">
<p class="text-sm font-semibold tracking-widest text-blue-400 uppercase mb-2">Reference</p>
<h2 class="text-2xl font-bold text-slate-100 mb-3">Reading Resistor Color Codes</h2>
<p class="text-sm text-slate-400 mb-8 max-w-2xl leading-relaxed">
Resistors in SpiceBook schematics use color-coded bands — the same EIA standard
printed on physical components. Each colored segment of the zigzag symbol encodes
a digit, multiplier, or tolerance value.
</p>
<div class="grid gap-8 lg:grid-cols-[1fr_1fr] items-start">
<!-- Left: Annotated resistor diagram -->
<div class="rcg-diagram-card">
<svg viewBox="0 0 460 200" xmlns="http://www.w3.org/2000/svg" class="w-full h-auto" aria-label="Annotated 10k ohm resistor showing 4 color bands">
<!-- Mims graph paper background -->
<defs>
<pattern id="rcg-minor" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 V 10 H 0" fill="none" stroke="#d4e4d4" stroke-width="0.3"/>
</pattern>
<pattern id="rcg-major" width="50" height="50" patternUnits="userSpaceOnUse">
<path d="M 50 0 V 50 H 0" fill="none" stroke="#b0ccb0" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="460" height="200" fill="#f8faf6" rx="4"/>
<rect width="460" height="200" fill="url(#rcg-minor)" rx="4"/>
<rect width="460" height="200" fill="url(#rcg-major)" rx="4"/>
<!-- Lead wires -->
<line x1="40" y1="100" x2="100" y2="100" stroke="#555" stroke-width="2"/>
<line x1="360" y1="100" x2="420" y2="100" stroke="#555" stroke-width="2"/>
<!-- Zigzag body — 7 segments like backend mapping -->
<!-- seg 0: entry half-rise (wire) -->
<polyline points="100,100 120,72" fill="none" stroke="#555" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- seg 1: Band 1 - Brown (1st digit = 1) -->
<polyline points="120,72 160,128" fill="none" stroke={bandHex.brown} stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<!-- seg 2: Band 2 - Black (2nd digit = 0) -->
<polyline points="160,128 200,72" fill="none" stroke={bandHex.black} stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<!-- seg 3: Band 3 - Orange (multiplier = ×1kΩ) -->
<polyline points="200,72 240,128" fill="none" stroke={bandHex.orange} stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<!-- seg 4: gap (wire) -->
<polyline points="240,128 280,72" fill="none" stroke="#555" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- seg 5: Band 4 - Gold (tolerance = ±5%) -->
<polyline points="280,72 320,128" fill="none" stroke={bandHex.gold} stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<!-- seg 6: exit half-rise (wire) -->
<polyline points="320,128 340,100" fill="none" stroke="#555" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Lead wire continuation -->
<line x1="340" y1="100" x2="360" y2="100" stroke="#555" stroke-width="2"/>
<!-- Callout labels with lines -->
<!-- Band 1: Brown -->
<line x1="140" y1="68" x2="140" y2="36" stroke="#888" stroke-width="0.8" stroke-dasharray="2,2"/>
<text x="140" y="30" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#555" font-weight="600">1st digit</text>
<text x="140" y="18" text-anchor="middle" font-family="ui-monospace, monospace" font-size="8.5" fill={bandHex.brown}>Brown = 1</text>
<!-- Band 2: Black -->
<line x1="180" y1="132" x2="180" y2="164" stroke="#888" stroke-width="0.8" stroke-dasharray="2,2"/>
<text x="180" y="175" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#555" font-weight="600">2nd digit</text>
<text x="180" y="188" text-anchor="middle" font-family="ui-monospace, monospace" font-size="8.5" fill="#444">Black = 0</text>
<!-- Band 3: Orange -->
<line x1="220" y1="68" x2="220" y2="36" stroke="#888" stroke-width="0.8" stroke-dasharray="2,2"/>
<text x="220" y="30" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#555" font-weight="600">Multiplier</text>
<text x="220" y="18" text-anchor="middle" font-family="ui-monospace, monospace" font-size="8.5" fill={bandHex.orange}>Orange = ×1k&#937;</text>
<!-- Band 4: Gold -->
<line x1="300" y1="132" x2="300" y2="164" stroke="#888" stroke-width="0.8" stroke-dasharray="2,2"/>
<text x="300" y="175" text-anchor="middle" font-family="ui-monospace, monospace" font-size="10" fill="#555" font-weight="600">Tolerance</text>
<text x="300" y="188" text-anchor="middle" font-family="ui-monospace, monospace" font-size="8.5" fill={bandHex.gold}>Gold = ±5%</text>
<!-- Result value label -->
<rect x="360" y="58" width="90" height="24" rx="3" fill="#f0f2ee" stroke="#b0ccb0" stroke-width="0.8"/>
<text x="405" y="74" text-anchor="middle" font-family="ui-monospace, monospace" font-size="11" fill="#333" font-weight="700">10 k&#937; ±5%</text>
</svg>
</div>
<!-- Right: Color code reference table -->
<div class="rcg-table-card">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-slate-700/60">
<th class="rcg-th text-left py-2.5 pr-2">Color</th>
<th class="rcg-th text-center py-2.5 px-2">Digit</th>
<th class="rcg-th text-left py-2.5 pl-2">Multiplier</th>
</tr>
</thead>
<tbody>
{digitBands.map((band) => (
<tr class="border-b border-slate-800/40">
<td class="py-1.5 pr-2">
<span class="inline-flex items-center gap-2">
<span class="rcg-dot" style={`background: ${band.hex};`}></span>
<span class="text-slate-300 text-xs">{band.color}</span>
</span>
</td>
<td class="text-center py-1.5 px-2 text-slate-300 font-mono text-xs">{band.digit}</td>
<td class="py-1.5 pl-2 text-slate-400 font-mono text-xs">{band.multiplier}</td>
</tr>
))}
</tbody>
</table>
<!-- Tolerance row -->
<div class="mt-4 pt-3 border-t border-slate-700/60">
<p class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">Tolerance</p>
<div class="flex gap-6">
{toleranceBands.map((band) => (
<span class="inline-flex items-center gap-2">
<span class="rcg-dot" style={`background: ${band.hex};`}></span>
<span class="text-slate-300 text-xs">{band.color}</span>
<span class="text-slate-400 font-mono text-xs">{band.value}</span>
</span>
))}
</div>
</div>
</div>
</div>
</section>
<style>
.rcg-diagram-card {
border-radius: 0.5rem;
border: 1px solid #1e293b;
overflow: hidden;
background: #f8faf6;
}
.rcg-table-card {
border-radius: 0.5rem;
border: 1px solid #1e293b;
background: rgba(15, 23, 42, 0.5);
padding: 1.25rem;
}
.rcg-th {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
}
.rcg-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
border: 1px solid rgba(148, 163, 184, 0.2);
}
</style>

View File

@ -34,8 +34,6 @@ export interface NotebookSummary {
tags: string[];
cell_count: number;
modified: string;
description?: string;
schematic_svg?: string;
}
export interface WaveformVariable {

View File

@ -3,7 +3,6 @@ import { Icon } from 'astro-icon/components';
import NotebookLayout from '../layouts/NotebookLayout.astro';
import NotebookGallery from '../components/NotebookGallery';
import PipelineStrip from '../components/PipelineStrip.astro';
import ResistorColorGuide from '../components/ResistorColorGuide.astro';
import FeaturedNotebooks from '../components/FeaturedNotebooks.astro';
import OscilloscopeDisplay from '../components/OscilloscopeDisplay.astro';
import { fetchNotebookList } from '../lib/server-api';
@ -73,11 +72,6 @@ try {
<PipelineStrip />
</section>
<!-- Resistor Color Code Guide -->
<section class="border-b border-slate-800/60">
<ResistorColorGuide />
</section>
<!-- Featured Notebooks -->
<section class="border-b border-slate-800/60">
<FeaturedNotebooks />