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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ repoUrl: https://... # optional GitHub repo URL
note: ... # optional note shown on detail page
```

The slug used for the URL is derived from the filename (e.g., `my-project.yml` → `/labs/my-project`).
The slug used for the URL is derived from the filename (e.g., `my-project.yml` → `/labs/my-project/`).

### Adding a talk entry

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ repoUrl: https://... # optional GitHub repo URL
note: ... # optional note shown on detail page
```

The slug used for the URL is derived from the filename (e.g., `my-project.yml` → `/labs/my-project`).
The slug used for the URL is derived from the filename (e.g., `my-project.yml` → `/labs/my-project/`).

### Adding a talk entry

Expand Down
6 changes: 5 additions & 1 deletion app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ const baseURL = runtimeConfig.app.baseURL

const basePrefix = baseURL.replace(/\/$/, '')

// Canonical URLs use the trailing-slash form (see `site.trailingSlash` in
// nuxt.config.ts); normalise here so a slash-less route never leaks through.
const canonicalUrl = computed(() => {
return new URL(`${basePrefix}${route.path || '/'}`, runtimeConfig.public.siteUrl).toString()
const path = route.path || '/'
const normalised = path.endsWith('/') ? path : `${path}/`
return new URL(`${basePrefix}${normalised}`, runtimeConfig.public.siteUrl).toString()
})

useHead({
Expand Down
3 changes: 2 additions & 1 deletion app/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const links = computed(() => navLinks.map(link => ({
})))

const isActive = (to: string) => {
return to === '/' ? route.path === '/' : route.path.startsWith(to)
const current = route.path.endsWith('/') ? route.path : `${route.path}/`
return to === '/' ? current === '/' : current.startsWith(to)
}
</script>

Expand Down
12 changes: 12 additions & 0 deletions app/components/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ const reveal = {
>&gt;_</span> {{ current.title }}
</p>

<p
v-if="page.hero.location"
class="signal-rise signal-rise-3 mt-2 flex items-center gap-1.5 font-mono text-sm tracking-[0.02em] text-muted"
>
<UIcon
name="i-lucide-map-pin"
class="size-3.5 shrink-0"
aria-hidden="true"
/>
<span>{{ page.hero.location }}</span>
</p>

<p class="signal-rise signal-rise-3 mt-7 max-w-xl text-xl leading-snug font-medium tracking-[-0.02em] text-pretty text-highlighted sm:text-2xl">
{{ intro.lead }}
</p>
Expand Down
4 changes: 2 additions & 2 deletions app/components/LabDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const reveal = {
</li>
<li>
<NuxtLink
to="/labs"
to="/labs/"
>labs</NuxtLink>
</li>
<li aria-current="page">
Expand Down Expand Up @@ -375,7 +375,7 @@ const reveal = {
eyebrow="Labs"
heading-id="signal-related-labs"
title="More from the lab"
:link="{ label: 'All labs', to: '/labs' }"
:link="{ label: 'All labs', to: '/labs/' }"
/>

<ul
Expand Down
4 changes: 2 additions & 2 deletions app/components/TalkDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const reveal = {
</li>
<li>
<NuxtLink
to="/speaking"
to="/speaking/"
>speaking</NuxtLink>
</li>
<li aria-current="page">
Expand Down Expand Up @@ -357,7 +357,7 @@ const reveal = {
eyebrow="Speaking"
heading-id="signal-related-talks"
title="More talks"
:link="{ label: 'All talks', to: '/speaking' }"
:link="{ label: 'All talks', to: '/speaking/' }"
/>

<ul
Expand Down
4 changes: 2 additions & 2 deletions app/pages/404.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ defineOgImage('Default')
</li>
<li>
<UButton
to="/labs"
to="/labs/"
icon="i-lucide-folder"
color="neutral"
variant="outline"
Expand All @@ -47,7 +47,7 @@ defineOgImage('Default')
</li>
<li>
<UButton
to="/speaking"
to="/speaking/"
icon="i-lucide-mic"
color="neutral"
variant="outline"
Expand Down
4 changes: 2 additions & 2 deletions app/pages/410.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ defineOgImage('Default')
</li>
<li>
<UButton
to="/labs"
to="/labs/"
icon="i-lucide-folder"
color="neutral"
variant="outline"
Expand All @@ -47,7 +47,7 @@ defineOgImage('Default')
</li>
<li>
<UButton
to="/speaking"
to="/speaking/"
icon="i-lucide-mic"
color="neutral"
variant="outline"
Expand Down
4 changes: 2 additions & 2 deletions app/pages/labs/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ if (!lab.value) {
usePageSeo(lab.value)

const siteUrl = runtimeConfig.public.siteUrl.replace(/\/$/, '')
const canonicalUrl = `${siteUrl}/labs/${slug}`
const canonicalUrl = `${siteUrl}/labs/${slug}/`
const identityId = `${siteUrl}/#identity`

useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: `${siteUrl}/` },
{ name: 'Labs', item: `${siteUrl}/labs` },
{ name: 'Labs', item: `${siteUrl}/labs/` },
{ name: lab.value.title, item: canonicalUrl }
]
}),
Expand Down
2 changes: 1 addition & 1 deletion app/pages/labs/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: `${siteUrl}/` },
{ name: 'Labs', item: `${siteUrl}/labs` }
{ name: 'Labs', item: `${siteUrl}/labs/` }
]
}),
defineItemList({
Expand Down
4 changes: 2 additions & 2 deletions app/pages/speaking/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ usePageSeo({
})

const siteUrl = runtimeConfig.public.siteUrl.replace(/\/$/, '')
const canonicalUrl = `${siteUrl}/speaking/${slug}`
const canonicalUrl = `${siteUrl}/speaking/${slug}/`
const identityId = `${siteUrl}/#identity`

useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: `${siteUrl}/` },
{ name: 'Speaking', item: `${siteUrl}/speaking` },
{ name: 'Speaking', item: `${siteUrl}/speaking/` },
{ name: talk.value.title, item: canonicalUrl }
]
}),
Expand Down
2 changes: 1 addition & 1 deletion app/pages/speaking/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: `${siteUrl}/` },
{ name: 'Speaking', item: `${siteUrl}/speaking` }
{ name: 'Speaking', item: `${siteUrl}/speaking/` }
]
}),
defineItemList({
Expand Down
2 changes: 1 addition & 1 deletion app/utils/labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const getLabSlug = <Lab extends Pick<LabEntry, 'stem' | 'title'>>(lab: La
}

export const getLabPath = <Lab extends Pick<LabEntry, 'stem' | 'title'>>(lab: Lab) => {
return `/labs/${getLabSlug(lab)}`
return `/labs/${getLabSlug(lab)}/`
}

export const formatLabDate = (value: string | Date) => {
Expand Down
4 changes: 2 additions & 2 deletions app/utils/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export const navLinks: NavigationMenuItem[] = [{
}, {
label: 'Labs',
icon: 'i-lucide-folder',
to: '/labs'
to: '/labs/'
}, {
label: 'Speaking',
icon: 'i-lucide-mic',
to: '/speaking'
to: '/speaking/'
}]
2 changes: 1 addition & 1 deletion app/utils/speaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const getTalkSlug = <Talk extends Pick<TalkEntry, 'stem' | 'title'>>(talk
}

export const getTalkPath = <Talk extends Pick<TalkEntry, 'stem' | 'title'>>(talk: Talk) => {
return `/speaking/${getTalkSlug(talk)}`
return `/speaking/${getTalkSlug(talk)}/`
}

const parseDurationMinutes = (duration?: string): number | null => {
Expand Down
16 changes: 8 additions & 8 deletions build/generate-content-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const renderLlmsTxt = (siteUrl: string, labs: LabEntry[], talks: TalkEntry[], ho
lines.push('## Labs')
lines.push('')
for (const lab of labs) {
lines.push(`- [${lab.title}](${siteUrl}/labs/${lab.slug}): ${collapseWhitespace(lab.description)}`)
lines.push(`- [${lab.title}](${siteUrl}/labs/${lab.slug}/): ${collapseWhitespace(lab.description)}`)
}
lines.push('')
}
Expand All @@ -108,7 +108,7 @@ const renderLlmsTxt = (siteUrl: string, labs: LabEntry[], talks: TalkEntry[], ho
lines.push('')
for (const talk of talks) {
const meta = [talk.event, talk.location, talk.dateLabel].filter(Boolean).join(' — ')
lines.push(`- [${talk.title}](${siteUrl}/speaking/${talk.slug}): ${collapseWhitespace(talk.summary)} (${meta})`)
lines.push(`- [${talk.title}](${siteUrl}/speaking/${talk.slug}/): ${collapseWhitespace(talk.summary)} (${meta})`)
}
lines.push('')
}
Expand All @@ -130,7 +130,7 @@ const renderLlmsFullTxt = (siteUrl: string, labs: LabEntry[], talks: TalkEntry[]
for (const lab of labs) {
const block: string[] = []
block.push(`### ${lab.title}`)
block.push(`URL: ${siteUrl}/labs/${lab.slug}`)
block.push(`URL: ${siteUrl}/labs/${lab.slug}/`)
if (lab.status) block.push(`Status: ${lab.status}`)
if (lab.tags?.length) block.push(`Tags: ${lab.tags.join(', ')}`)
if (lab.url) block.push(`Demo: ${lab.url}`)
Expand Down Expand Up @@ -161,7 +161,7 @@ const renderLlmsFullTxt = (siteUrl: string, labs: LabEntry[], talks: TalkEntry[]
for (const talk of talks) {
const block: string[] = []
block.push(`### ${talk.title}`)
block.push(`URL: ${siteUrl}/speaking/${talk.slug}`)
block.push(`URL: ${siteUrl}/speaking/${talk.slug}/`)
block.push(`Event: ${talk.event}`)
block.push(`Location: ${talk.location}`)
if (talk.dateLabel) block.push(`Date: ${talk.dateLabel}`)
Expand Down Expand Up @@ -197,8 +197,8 @@ const renderRssXml = (siteUrl: string, labs: LabEntry[], talks: TalkEntry[]) =>
for (const lab of labs) {
pushItem({
title: lab.title,
link: `${siteUrl}/labs/${lab.slug}`,
guid: `${siteUrl}/labs/${lab.slug}`,
link: `${siteUrl}/labs/${lab.slug}/`,
guid: `${siteUrl}/labs/${lab.slug}/`,
pubDate: formatDate(lab.date),
category: 'Labs',
description: collapseWhitespace(lab.description)
Expand All @@ -207,8 +207,8 @@ const renderRssXml = (siteUrl: string, labs: LabEntry[], talks: TalkEntry[]) =>
for (const talk of talks) {
pushItem({
title: talk.title,
link: `${siteUrl}/speaking/${talk.slug}`,
guid: `${siteUrl}/speaking/${talk.slug}`,
link: `${siteUrl}/speaking/${talk.slug}/`,
guid: `${siteUrl}/speaking/${talk.slug}/`,
pubDate: formatDate(talk.date),
category: 'Speaking',
description: collapseWhitespace(talk.summary)
Expand Down
1 change: 1 addition & 0 deletions content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default defineContentConfig({
hero: z.object({
name: z.string(),
role: z.string(),
location: z.string().optional(),
intro: z.string()
}),
focus: createBaseSchema().extend({
Expand Down
5 changes: 3 additions & 2 deletions content/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ description: Staff Agentic Engineer at Cordes & Graefe KG focused on organisatio
hero:
name: Fabian Meyer
role: "Staff Agentic Engineer at Cordes\u00A0&\u00A0Graefe\u00A0KG"
location: Bremen, Germany
intro: I help companies make the move to agentic engineering. That means shaping how teams work with agents, building the tooling and workflows that make the change stick, and staying hands-on so the practices hold up in real delivery.
focus:
title: Focus
Expand Down Expand Up @@ -69,12 +70,12 @@ labs:
link:
label: Explore Labs
icon: i-lucide-flask-conical
to: /labs
to: /labs/

speaking:
title: Speaking
description: Talks, sessions, and speaking work I want to keep building over time.
link:
label: View Speaking
icon: i-lucide-mic-vocal
to: /speaking
to: /speaking/
Loading