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
30 changes: 28 additions & 2 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,35 @@ import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import path from 'path';

/**
* Inject a default `layout:` into MDX frontmatter when the file lives under
* a content-page directory and the author didn't set one. Lets posts skip
* the boilerplate header line in 99% of cases.
*/
function injectDefaultLayout(layoutPath, includeDirs) {
return () => (_tree, file) => {
const fm = file?.data?.astro?.frontmatter;
if (!fm || fm.layout) return;
const sourcePath = file.history?.[0] ?? file.path ?? '';
if (!includeDirs.some((dir) => sourcePath.includes(dir))) return;
fm.layout = layoutPath;
};
}

export default defineConfig({
site: 'https://viniciusdc.github.io',
integrations: [mdx({ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex] })],
integrations: [
mdx({
remarkPlugins: [
remarkMath,
injectDefaultLayout('@/layouts/ArticleLayout.astro', [
'/pages/writing/',
'/pages/projects/',
]),
],
rehypePlugins: [rehypeKatex],
}),
],
devToolbar: { enabled: false },
vite: {
plugins: [tailwindcss()],
Expand All @@ -18,4 +44,4 @@ export default defineConfig({
},
},
},
});
});
2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"keycloak", "oidc", "oauth", "saml", "jwt", "jwks",
"fastapi", "pydantic", "scrapy", "uvicorn",
"vinicius", "cerutti", "Ceture", "viniciusdc", "Catarina", "UFSC",
"astro", "tailwind", "shadcn", "fontsource", "lhci", "lightbox",
"astro", "tailwind", "shadcn", "fontsource", "lhci", "lightbox", "frontmatter",
"woff", "woff2", "oklch", "rgb", "rgba", "hsl",
"clamp", "minmax", "prefers",
"nebari", "jmespath", "snistrict",
Expand Down
68 changes: 61 additions & 7 deletions src/layouts/ArticleLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,72 @@ import Layout from "@/layouts/Layout.astro";
import PageRule from "@/components/ui/PageRule.astro";
import ImageLightbox from "@/components/ui/ImageLightbox.astro";

interface Props {
title: string;
/**
* Two ways to use this layout:
*
* 1. As an Astro layout reference from MDX/MD frontmatter:
* ---
* layout: '@/layouts/ArticleLayout.astro'
* title: ...
* date: 2026-05-18
* tag: meta
* description: ...
* ---
* The body markdown renders into the layout's <slot />. `meta` is
* auto-composed as "date · tag" when both are present.
*
* 2. As a wrapping component from an `.astro` page that needs JSX/props:
* <ArticleLayout title="..." meta="..." subtitle="..." backHref="/projects/" ...>
* <h2>...</h2>
* </ArticleLayout>
*/
interface Frontmatter {
title?: string;
description?: string;
meta: string;
subtitle: string;
backHref: string;
backLabel: string;
date?: string;
tag?: string;
subtitle?: string;
meta?: string;
backHref?: string;
backLabel?: string;
nextHref?: string;
nextLabel?: string;
}

const { title, description, meta, subtitle, backHref, backLabel, nextHref, nextLabel } = Astro.props;
interface Props extends Frontmatter {
frontmatter?: Frontmatter;
}

const fm = Astro.props.frontmatter ?? {};
const title = Astro.props.title ?? fm.title ?? "Untitled";
const description = Astro.props.description ?? fm.description;
const subtitle = Astro.props.subtitle ?? fm.subtitle ?? description ?? "";
const backHref = Astro.props.backHref ?? fm.backHref ?? "/writing/";
const backLabel = Astro.props.backLabel ?? fm.backLabel ?? "writing";
const nextHref = Astro.props.nextHref ?? fm.nextHref;
const nextLabel = Astro.props.nextLabel ?? fm.nextLabel;

// YAML auto-parses ISO dates into Date objects. Normalize to YYYY-MM-DD
// so authors can write `date: 2026-05-18` without remembering to quote it.
// `instanceof Date` can fail across realms; duck-type instead.
function asDateString(d: unknown): string | undefined {
if (!d) return undefined;
if (typeof (d as { toISOString?: () => string }).toISOString === "function") {
return (d as Date).toISOString().slice(0, 10);
}
// Already a string — strip any trailing ISO time component just in case.
return String(d).slice(0, 10);
}

const dateStr = asDateString(fm.date);

// Compose `meta` if not explicit:
// posts: "YYYY-MM-DD · tag"
// projects: "label"
const explicitMeta = Astro.props.meta ?? fm.meta;
const labelOrTag = fm.tag ?? (fm as { label?: string }).label;
const composedMeta = [dateStr, labelOrTag].filter(Boolean).join(" · ");
const meta = explicitMeta ?? composedMeta;
---

<Layout title={`${title} — Vinicius's notebook`} description={description ?? subtitle}>
Expand Down
23 changes: 19 additions & 4 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ interface PostMeta {
tag: string;
description: string;
}
interface PostModule { metadata?: PostMeta; }
// `.mdx`/`.md` files with YAML frontmatter expose it as `frontmatter`.
// `.astro` files (and `.mdx` files using `export const metadata`) expose `metadata`.
// Accept either so we can mix patterns during the migration.
interface PostModule { metadata?: PostMeta; frontmatter?: PostMeta; }

function toHref(base: string, path: string) {
return base + path.replace(/^\.\/[^/]+/, '').replace(/\/index\.(astro|mdx)$/, '/');
Expand All @@ -19,9 +22,21 @@ function toHref(base: string, path: string) {
const writingAstro = import.meta.glob<PostModule>('./writing/*/index.astro', { eager: true });
const writingMdx = import.meta.glob<PostModule>('./writing/*/index.mdx', { eager: true });

const allRecent = [
...Object.entries({ ...writingAstro, ...writingMdx }).map(([p, m]) => ({ href: toHref('/writing', p), ...m.metadata! })),
]
// YAML auto-parses ISO dates into Date objects; normalize so sort/compare works.
// `instanceof Date` can fail across realms; duck-type instead.
function asDateString(d: unknown): string | undefined {
if (!d) return undefined;
if (typeof (d as { toISOString?: () => string }).toISOString === "function") {
return (d as Date).toISOString().slice(0, 10);
}
return String(d).slice(0, 10);
}

const allRecent = Object.entries({ ...writingAstro, ...writingMdx })
.map(([p, m]) => {
const data = m.frontmatter ?? m.metadata ?? {} as PostMeta;
return { href: toHref('/writing', p), ...data, date: asDateString(data.date) };
})
.filter(p => p.title && p.date)
.sort((a, b) => b.date!.localeCompare(a.date!));

Expand Down
40 changes: 40 additions & 0 deletions src/pages/writing/beginning-again/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Beginning again
date: 2026-05-18
tag: meta
description: "Notes on starting a notebook — what's here, what isn't, what I'm trying not to do."
---

I've started a blog several times. Most attempts stalled within a few months. Partly because I picked a niche too narrow — *Kubernetes field notes* was the last one — and partly because I kept forgetting what writing is actually *for*.

For me it isn't for an audience. It's for thinking.

I'll have a vague intuition about something, sit down to write a paragraph, and ten minutes in realize the intuition didn't survive contact with sentences. The diagram I had in my head turns out not to compile. The reading I was working from doesn't quite say what I thought it said. That's the value: writing finds the gaps that thinking-in-your-head papers over.

So this notebook is a different premise from the ones before. It's for me. Kept in public because writing for a vague hypothetical *someone* forces a level of clarity that private notes never reach — but the audience is incidental.

## What lives here

Mostly whatever has my attention.

Software, often — Kubernetes most of my paid work: bare-metal clusters, identity, GPU runtimes, the small failures that take a week to find. But also AI papers I'm chewing on, math I keep wandering back to (my degree is in applied math; I haven't found a way to leave it alone), design ideas, games that won't leave me alone, the occasional philosophical thing without a clean answer.
Comment thread
viniciusdc marked this conversation as resolved.

The connective tissue isn't a topic. It's me.

## What I'm trying not to do

Wait for things to be finished. A lot of what shows up here will be drafts I want to come back to. Some of it will be wrong — I'll either fix the post or leave a note saying so — and that's fine. Waiting for perfect is how I lost the last several years of writing I meant to do.

Chase an audience either. If you're here, hi, and thank you. But the bar for whether a post exists is whether I learned something from writing it, not whether anyone else learned something reading it.

## What I'm trying to do

Write small. Short notes. Reactions. Sketches of ideas. Not every entry needs to be an essay.

Write honestly. If I don't understand something, I want the post to show that. If I change my mind later, I want the next post to show that too. The pretense of always-having-known is exhausting and not even useful.

Write more often than I think is sensible.

## See you in a bit

That's it for now. If you find something worth reading here — or worth correcting — please tell me.
23 changes: 17 additions & 6 deletions src/pages/writing/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import PageRule from "@/components/ui/PageRule.astro";

interface PostMeta { title: string; date?: string; tag: string; description: string; }
interface DebugMeta { label: string; title: string; description: string; tags: string[]; href?: string; }
interface PostModule { metadata?: PostMeta | DebugMeta; }
// Accept either `frontmatter` (YAML at the top of .mdx) or `metadata` (export const).
interface PostModule { metadata?: PostMeta | DebugMeta; frontmatter?: PostMeta | DebugMeta; }

function toHref(path: string) {
return '/writing' + path.replace(/^\.\//, '/').replace(/\/index\.(astro|mdx)$/, '/');
Expand All @@ -19,14 +20,24 @@ const allModules = {
...import.meta.glob<PostModule>('./*/index.mdx', { eager: true }),
};

// YAML auto-parses ISO dates into Date objects; normalize so sort/compare works.
// `instanceof Date` can fail across realms; duck-type instead.
function asDateString(d: unknown): string | undefined {
if (!d) return undefined;
if (typeof (d as { toISOString?: () => string }).toISOString === "function") {
return (d as Date).toISOString().slice(0, 10);
}
return String(d).slice(0, 10);
}

const entries = Object.entries(allModules)
.filter(([, m]) => m.metadata)
.map(([path, m]) => {
const meta = m.metadata!;
.map(([path, m]) => [path, m.frontmatter ?? m.metadata] as const)
.filter((tuple): tuple is readonly [string, PostMeta | DebugMeta] => tuple[1] != null)
.map(([path, meta]) => {
if (isDebug(meta)) {
return { href: meta.href ?? toHref(path), title: meta.title, date: undefined, tag: meta.tags[0] ?? 'debugging', description: meta.description };
return { href: meta.href ?? toHref(path), title: meta.title, date: undefined as string | undefined, tag: meta.tags[0] ?? 'debugging', description: meta.description };
}
return { href: toHref(path), title: meta.title, date: meta.date, tag: meta.tag, description: meta.description };
return { href: toHref(path), title: meta.title, date: asDateString(meta.date), tag: meta.tag, description: meta.description };
})
.sort((a, b) => (b.date ?? '').localeCompare(a.date ?? ''));

Expand Down
28 changes: 8 additions & 20 deletions src/templates/architecture.mdx
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
import ArticleLayout from "@/layouts/ArticleLayout.astro";

export const metadata = {
title: "The decision or system being described",
date: "YYYY-MM-DD",
tag: "architecture",
description: "What this essay argues or explains — one sentence.",
};
---
title: The decision or system being described
date: YYYY-MM-DD
tag: architecture
description: What this essay argues or explains — one sentence.
---

{/*
Checklist before publishing:
[ ] Title frames the decision, not just the component ("X as Y" or "Why we shaped X this way")
[ ] metadata.description describes what the essay argues or explains
[ ] metadata.tag matches the area: operator design · state systems · platform · reliability
[ ] description describes what the essay argues or explains
[ ] tag matches the area: operator design · state systems · platform · reliability
[ ] Sections flow: context → constraint → decision → tradeoffs
[ ] No placeholder text remains
*/}

<ArticleLayout
title={metadata.title}
meta={`${metadata.date} · ${metadata.tag}`}
subtitle={metadata.description}
backHref="/writing/"
backLabel="← writing"
>

## Context

{/* What is the system or problem space? What forces or constraints shaped this? Keep it tight. */}
Expand All @@ -43,5 +33,3 @@ export const metadata = {
## Boundaries

{/* Where does this design stop? What's explicitly out of scope? */}

</ArticleLayout>
31 changes: 9 additions & 22 deletions src/templates/note.mdx
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import ArticleLayout from "@/layouts/ArticleLayout.astro";

export const metadata = {
title: "Short, specific title describing the exact problem",
date: "YYYY-MM-DD",
tag: "field note",
description: "One sentence. What does this note explain and why does it matter.",
};
---
title: Short, specific title describing the exact problem
date: YYYY-MM-DD
tag: field note
description: One sentence. What does this note explain and why does it matter.
---

{/*
Checklist before publishing:
[ ] Title is specific — describes the exact problem, not just the area
[ ] metadata.date is accurate (YYYY-MM-DD)
[ ] metadata.tag is one of: field note · debugging · security · architecture · platform
[ ] metadata.description is one sentence — states what the note explains
[ ] backHref/nextHref links are correct
[ ] date is accurate (YYYY-MM-DD)
[ ] tag is one of: field note · debugging · security · architecture · platform
[ ] description is one sentence — states what the note explains
[ ] No placeholder text remains
*/}

<ArticleLayout
title={metadata.title}
meta={`${metadata.date} · ${metadata.tag}`}
subtitle={metadata.description}
backHref="/writing/"
backLabel="← writing"
>

## Background

{/* What were you doing when you hit this? Give just enough context. One short paragraph. */}
Expand All @@ -48,5 +37,3 @@ error message or log output here
```bash
# commands here
```

</ArticleLayout>
Loading