diff --git a/data/samples/html/dark_dev.html b/data/samples/html/dark_dev.html
index 292e3ac..d1ccd0b 100644
--- a/data/samples/html/dark_dev.html
+++ b/data/samples/html/dark_dev.html
@@ -1,25 +1,208 @@
-
+
+
+
+
+
- Ship small PRs
Keep changes reviewable and tested.
+
+ Ship small PRs
+ 2026-07-28 · 4 min read
+ Keep changes reviewable and tested. Prefer a focused diff over a weekend mega-merge that nobody can reason about.
+ fn main() {
+ println!("reviewable > clever");
+}
+
+ rust
+ ci
+
+
+
+ Typed boundaries beat tribal knowledge
+ 2026-07-12 · 6 min read
+ Document contracts in types and tests. When onboarding gets expensive, your interfaces were never clear enough.
+ export function claim(issue: number) {
+ return { status: "claimed", issue };
+}
+
+ typescript
+ python
+
+
+
+ Night-shift checklist
+ 2026-06-30 · 3 min read
+ Reproduce once. Fix once. Leave a regression test. Push before sunrise.
+
+ go
+ ops
+
+
-
+
-
+
diff --git a/docs/screenshots/dark_dev-desktop.png b/docs/screenshots/dark_dev-desktop.png
new file mode 100644
index 0000000..c35ea59
Binary files /dev/null and b/docs/screenshots/dark_dev-desktop.png differ
diff --git a/docs/screenshots/dark_dev-mobile.png b/docs/screenshots/dark_dev-mobile.png
new file mode 100644
index 0000000..e97de2d
Binary files /dev/null and b/docs/screenshots/dark_dev-mobile.png differ
diff --git a/docs/screenshots/demo-dark_dev.png b/docs/screenshots/demo-dark_dev.png
index c289c5e..8a0e62c 100644
Binary files a/docs/screenshots/demo-dark_dev.png and b/docs/screenshots/demo-dark_dev.png differ
diff --git a/src/bloggereasy/cli.py b/src/bloggereasy/cli.py
index 716a03a..8f9d0ef 100644
--- a/src/bloggereasy/cli.py
+++ b/src/bloggereasy/cli.py
@@ -129,7 +129,7 @@ def demo_cmd(
template_for = {
"portfolio.html": "portfolio",
"news_portal.html": "news",
- "dark_dev.html": "dark",
+ "dark_dev.html": "dark_dev",
}
for path in samples:
out = root / f"{path.stem}.xml"
@@ -329,6 +329,7 @@ def _validate_widgets(widgets: str) -> None:
def _sample_for_template(template: str) -> Path:
sample_map = {
"dark": "dark_dev.html",
+ "dark_dev": "dark_dev.html",
"docs": "docs_blog.html",
"food_recipe": "food_recipe.html",
"from-image": "portfolio.html",
diff --git a/src/bloggereasy/gui/main_window.py b/src/bloggereasy/gui/main_window.py
index ffece22..68da795 100644
--- a/src/bloggereasy/gui/main_window.py
+++ b/src/bloggereasy/gui/main_window.py
@@ -420,7 +420,7 @@ def _pick_sample(self) -> None:
mapping = {
"portfolio.html": "portfolio",
"news_portal.html": "news",
- "dark_dev.html": "dark",
+ "dark_dev.html": "dark_dev",
"magazine.html": "magazine",
}
self.template.setCurrentText(mapping.get(self._html_path.name, "simple"))
@@ -672,7 +672,7 @@ def run_demo_batch(self) -> None:
template_for = {
"portfolio.html": "portfolio",
"news_portal.html": "news",
- "dark_dev.html": "dark",
+ "dark_dev.html": "dark_dev",
"magazine.html": "magazine",
}
for path in samples:
diff --git a/src/bloggereasy/theme/presets.py b/src/bloggereasy/theme/presets.py
index 36bae81..e638f44 100644
--- a/src/bloggereasy/theme/presets.py
+++ b/src/bloggereasy/theme/presets.py
@@ -62,6 +62,12 @@
"dense": False,
"accent": "#0055aa",
},
+ "dark_dev": {
+ "layout_hint": "two-column",
+ "dark": True,
+ "dense": False,
+ "accent": "#38bdf8",
+ },
}
@@ -77,7 +83,7 @@ def apply_preset(structure: PageStructure | dict, template: str) -> dict:
out["features"] = feats
if preset.get("dark"):
out = apply_dark_variant(out)
- if preset.get("accent") and not preset.get("dark"):
+ if preset.get("accent"):
colors = dict(out.get("colors") or {})
colors["primary"] = preset["accent"]
out["colors"] = colors
diff --git a/tests/test_theme_dark_dev.py b/tests/test_theme_dark_dev.py
new file mode 100644
index 0000000..416ed3f
--- /dev/null
+++ b/tests/test_theme_dark_dev.py
@@ -0,0 +1,56 @@
+"""Theme pack sample: dark_dev (Fixes #24).
+
+Verifies the dark_dev preset generates valid Blogger XML from the
+self-contained HTML fixture, containing a b:skin block and a Blog widget.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from bloggereasy.integrations.sdk import generate_from_html
+from bloggereasy.theme.presets import PRESETS
+from bloggereasy.theme.validate import validate_theme_file
+
+SAMPLES = Path(__file__).resolve().parents[1] / "data" / "samples" / "html"
+
+
+def test_dark_dev_preset_registered() -> None:
+ assert "dark_dev" in PRESETS
+ assert PRESETS["dark_dev"]["dark"] is True
+ assert PRESETS["dark_dev"]["accent"] == "#38bdf8"
+ assert PRESETS["dark_dev"]["layout_hint"] == "two-column"
+
+
+def test_dark_dev_sample_exists() -> None:
+ src = SAMPLES / "dark_dev.html"
+ assert src.is_file()
+ html = src.read_text(encoding="utf-8")
+ assert "Dev Notes" in html
+ assert "#0b1220" in html or "#0B1220" in html
+ assert "sidebar" in html
+
+
+def test_gen_html_dark_dev_produces_skin_and_blog_widget(tmp_path: Path) -> None:
+ src = SAMPLES / "dark_dev.html"
+ out = tmp_path / "dark_dev.xml"
+ result = generate_from_html(src, out, template="dark_dev")
+
+ assert result["validation"]["ok"], result["validation"]
+ assert out.exists()
+
+ structure = result["structure"]
+ assert structure["layout"] == "two-column"
+ assert structure.get("features", {}).get("dark") is True
+
+ xml = out.read_text(encoding="utf-8")
+ assert "xmlns:b=" in xml
+ assert "b:skin" in xml
+ assert "type='Blog'" in xml or 'type="Blog"' in xml
+ # dark_dev accent + dark surface should land in the skin
+ assert "38bdf8" in xml
+ assert "#0f172a" in xml or "0f172a" in xml
+ assert "Template: dark_dev" in xml
+
+ v = validate_theme_file(out)
+ assert v["ok"] is True
diff --git a/tests/test_theme_snapshots.py b/tests/test_theme_snapshots.py
index fd434f6..84ba98f 100644
--- a/tests/test_theme_snapshots.py
+++ b/tests/test_theme_snapshots.py
@@ -9,7 +9,7 @@
def test_dark_and_magazine_generate_valid_xml(tmp_path: Path) -> None:
samples = Path("data/samples/html")
cases = [
- ("dark_dev.html", "dark"),
+ ("dark_dev.html", "dark_dev"),
("magazine.html", "magazine"),
("contact_card.html", "simple"),
("resume_cv.html", "simple"),
@@ -26,8 +26,8 @@ def test_dark_and_magazine_generate_valid_xml(tmp_path: Path) -> None:
xml = out.read_text(encoding="utf-8")
assert "b:skin" in xml or "skin" in xml.lower()
assert "Blog" in xml
- # dark preset should push dark-ish background into skin
- if tmpl == "dark":
+ # dark / dark_dev presets should push dark-ish background into skin
+ if tmpl in {"dark", "dark_dev"}:
assert "#0f172a" in xml or "0f172a" in xml
v = validate_theme_file(out)
assert v["ok"] is True