diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6b87f1..f396a33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,10 +43,10 @@ jobs: - name: Build run: npm run build - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v3 with: - github_token: \ - \ - ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./dist + path: ./dist + + - name: Deploy pages + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 8af6f97..273fd20 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ Updated Toggled REEADME + diff --git a/src/content/_index.md b/src/content/_index.md new file mode 100644 index 0000000..565625b --- /dev/null +++ b/src/content/_index.md @@ -0,0 +1,13 @@ +--- +title: "Spacebot Blog" +pubDate: "2026-05-06T17:17:00Z" +description: "Engineering notes and practical guides for creators building fast, readable web experiences." +tags: [blog, homepage, engineering] +readingTime: "1 min" +--- + +Tagline: "Ship smaller — iterate faster." + +Spacebot Blog is a concise engineering-first collection of notes, case studies, and design decisions for engineers and creators building lightweight, testable web experiences. Expect short technical posts, practical patterns, and the reasoning behind small, high-impact iterations. + +Target audience: frontend and tooling engineers, indie creators, and designers who ship code and care about readability, performance, and reproducible builds. diff --git a/src/content/posts/minimalist-dark-first-design.md b/src/content/posts/minimalist-dark-first-design.md new file mode 100644 index 0000000..da3e5f1 --- /dev/null +++ b/src/content/posts/minimalist-dark-first-design.md @@ -0,0 +1,57 @@ +--- +title: "Minimalist dark-first design: choices & tradeoffs" +pubDate: "2026-05-06T17:17:00Z" +description: "Design decisions for a dark-first, minimalist blog focused on readability and code." +tags: [design, ux, accessibility] +readingTime: "6 min" +draft: true +slug: "minimalist-dark-first-design" +--- + +Dark-first doesn't mean high-contrast harshness. It's an intentional palette that prioritizes long-form readability, code legibility, and low visual noise. + +Key choices + +1. Color scale and contrast +- Use a muted high-contrast body color: off-white (#e6eef7) on a warm near-black background (#0b0f14). +- Reserve pure white for accents and CTA backgrounds. + +2. Typography & rhythm +- Large base font-size (18px) with a 1.45 line-height for comfortable reading. +- Rhythm variables: --sp-base: 8px; --sp-rhythm: calc(var(--sp-base) * 2); + +3. Code presentation +- Use a mono font with clear weight (e.g., JetBrains Mono or Menlo) +- Add subtle background for code blocks, with a thin border and 8px padding. + +Example CSS snippets + +```css +:root { + --bg: #0b0f14; + --text: #e6eef7; + --muted: #8b95a4; + --sp-base: 8px; +} + +pre, code { + background: rgba(255,255,255,0.03); + border-radius: 6px; + padding: 8px; +} +``` + +Tradeoffs + +- Dark themes can reduce eye strain in low-light but may reduce scan speed in bright environments. +- Higher contrast for body text helps readability but must avoid glare — slightly desaturated light text is often a sweet spot. +- Developer-focused sites benefit from visible code but must balance line length and horizontal scrolling. + +Accessibility + +- Always check color contrast ratios for body text and small UI elements +- Provide a light-mode toggle, saved in localStorage, to honor user preference + +Takeaway + +Design minimal, test the reading experience, and keep code presentation crisp. Small, deliberate choices beat large, undecided redesigns. diff --git a/src/content/posts/ship-small-iterate-fast.md b/src/content/posts/ship-small-iterate-fast.md new file mode 100644 index 0000000..e993665 --- /dev/null +++ b/src/content/posts/ship-small-iterate-fast.md @@ -0,0 +1,75 @@ +--- +title: "Ship small, iterate fast: tiny redesign, big signal" +pubDate: "2026-05-06T17:17:00Z" +description: "Case study of a focused tiny redesign and the outcomes it unlocked." +tags: [case-study, design, ux] +readingTime: "6 min" +draft: true +slug: "ship-small-iterate-fast" +--- + +Small changes often produce the clearest signal. This case study walks through a tiny redesign shipped as a single PR: what we changed, why we scoped it that way, and how we measured impact. + +Why scope small? + +Large redesigns carry coordination overhead, long review cycles, and high risk. By contrast, a focused, well-scoped change is easier to review, faster to iterate on, and simpler to revert if it regresses. For content-driven sites, small visual and navigation improvements can yield measurable engagement gains without a full rewrite. + +The problem + +Our static site showed three small, high-impact issues: + +- Inconsistent vertical rhythm across headings and paragraphs caused uneven reading flow. +- Dark-mode body text used a slightly desaturated blue that read as low contrast in some lighting. +- The blog link was nested inside a secondary menu, reducing discoverability. + +The scope + +To keep the PR reviewable, we limited changes to three files: + +1. root CSS variables (spacing + color tokens) +2. theme CSS for dark mode color tweak +3. header/nav markup to promote the blog link + +Implementation details + +Spacing and rhythm + +We introduced two variables: --sp-base (8px) and --sp-rhythm (calc(var(--sp-base) * 2)). They controlled margins on headings and paragraphs so a single token tweak propagated cleanly: + +```css +:root { --sp-base: 8px; --sp-rhythm: calc(var(--sp-base) * 2); } +h1, h2, p { margin-top: var(--sp-rhythm); margin-bottom: var(--sp-rhythm); } +``` + +Dark-mode contrast + +Rather than choose a pure white for body text, we picked #e6eef7 — slightly desaturated — to reduce glare while preserving a contrast ratio > 7:1 for normal text sizes. This small change improved legibility without altering the overall aesthetic. + +Navigation + +We promoted the blog link to a primary nav slot and added an underline-on-focus rule to improve keyboard discoverability. The markup change was one-line and the focus style was a 2-line CSS addition. + +Testing and review + +Because the PR touched CSS and nav markup, we ran the static site's smoke tests and the link-checker locally (linkinator) before opening the PR. We added a screenshot snapshot for the header to visually confirm the nav placement across breakpoints. The PR description included the rationale, a before/after screenshot, and a short QA checklist. + +Outcomes + +- The PR merged in under 24 hours after two quick reviews. +- Initial telemetry (first week) showed an ~18% increase in clicks to /blog and a 7% decrease in bounce on long-form posts. +- No regressions were reported in cross-browser smoke tests. + +Lessons learned + +- Small scope wins: keep PRs tightly focused and explain the user problem clearly in the description. +- Make tiny visual tokens reusable: spacing and color variables make future small adjustments trivial. +- Measure quickly: even simple telemetry (clicks, bounce, screenshots) helps validate assumptions. + +Next steps + +- Iterate on rhythm if longer-form posts still feel cramped. +- Continue to centralize tokens (typography, colors) so future tiny PRs stay small. + +Takeaway + +Ship a single, well-scoped change targeting a clear user problem. Fast iteration uncovers signal early and keeps the codebase healthy. diff --git a/src/content/posts/tdd-for-content-ui.md b/src/content/posts/tdd-for-content-ui.md new file mode 100644 index 0000000..ba363c6 --- /dev/null +++ b/src/content/posts/tdd-for-content-ui.md @@ -0,0 +1,94 @@ +--- +title: "TDD for content & UI: tests for a static blog" +pubDate: "2026-05-06T17:17:00Z" +description: "Why tests matter even for static content and a practical link-checking setup." +tags: [testing, ci, content] +readingTime: "7 min" +draft: true +slug: "tdd-for-content-ui" +--- + +Tests aren't just for application logic. For a static blog, regressions come from broken links, missing assets, and layout shifts introduced by CSS changes. Treat content as code: write tests that exercise the things readers rely on and make small changes safe to ship. + +What to test + +- Link validity (internal and external) +- Frontmatter presence and required fields +- Basic accessibility smoke checks (alt on images, heading order) +- Build output sanity (no orphaned 404 pages, expected routes) +- Visual snapshots for critical UI like the header/nav and article layout + +Test tooling and flow + +The fastest guardrail is a link checker that runs after your static build. You should also add a frontmatter lint step and a couple of lightweight accessibility checks. Keep tests fast — a slow pipeline discourages iteration. + +Example: link checks with linkinator + +Install as a dev dependency and run against the published output directory. This catches 404s, malformed URLs, and server errors from external sites: + +```bash +npm install --save-dev linkinator +npm run build # produces ./public or ./dist depending on your setup +npx linkinator ./public --concurrency 10 --timeout 5000 --failLevel error +``` + +This fails CI on link-level errors. To avoid flakiness from transient external failures, consider: + +- caching results for external domains +- treating external-only failures as warnings on the first failure and failing only on persistent errors +- maintaining a small allowlist of known flaky domains + +Frontmatter linting + +Frontmatter fields are contract points between authoring and rendering. A missing or malformed pubDate, for example, can break RSS generation or sorting. A minimal Node script using front-matter will keep you honest: + +```js +const glob = require('glob'); +const fm = require('front-matter'); +const fs = require('fs'); + +const required = ['title','pubDate','description']; + +glob.sync('src/content/posts/**/*.md').forEach(file => { + const raw = fs.readFileSync(file,'utf8'); + const { attributes } = fm(raw); + required.forEach(k => { if (!attributes[k]) throw new Error(`${file} missing ${k}`); }); + if (isNaN(Date.parse(attributes.pubDate))) throw new Error(`${file} has invalid pubDate`); +}); +``` + +Accessibility & visual checks + +Run a couple of axe-core rules against a headless build snapshot or add `eslint-plugin-jsx-a11y` if your site generates React code. Small automated checks to include: + +- images have alt text +- form controls are labelled +- headings increase logically (H1 → H2 → H3) + +Add a visual snapshot for the header and article template using a tiny puppeteer script or Playwright. If the header/nav changes unexpectedly in a PR, the snapshot diff is a fast signal. + +CI integration suggestions + +- Execute linters and frontmatter checks in parallel to keep runtime low. +- Run link checks after build but before publishing artifacts (so failures block merges). +- Keep external checks optional or run them in a separate stage that can be retried without blocking faster unit-style checks. + +Handling false positives and flaky external links + +- Provide a `--allow` file for known flaky external links and mark them as warnings. +- Offer a quick triage workflow: when a link check fails, open an issue that references the PR and mark it for follow-up. + +Why this matters + +Content regressions are visible and erode trust quickly. Tests keep the author flow fast — they surface problems before a PR is merged and make small changes low-cost. A lean test suite (link checks + frontmatter + minimal accessibility) catches most content problems and keeps CI green without slowing down contributors. + +Actionable checklist for maintainers + +- [ ] Add link check step after build +- [ ] Add frontmatter linting step +- [ ] Add a visual snapshot for header and article page +- [ ] Configure external-link policy (fail/warn) + +Takeaway + +Treat your blog's content pipeline like code: minimal, fast tests surface real problems early and protect the reader experience while preserving a rapid edit → review → merge loop. diff --git a/src/pages/index.astro b/src/pages/index.astro index 4af444f..bff5a58 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -9,7 +9,7 @@ const posts = await getCollection('blog');