feat: implement talks pages with content and UI#15
Conversation
📝 WalkthroughWalkthroughAdds a Talks feature: content schemas and 15 talk markdown files, new pages and UI components (talk list, talk detail, TalkCard, testimonials carousel), OG image components (avatar, glow, footer, talk OG), switches avatar refs from .webp to .jpg, and adds duplicated Satori rendering instructions in .coderabbit.yml. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/components/OgImage/OgImageHome.vue (1)
80-104: 🧹 Nitpick | 🔵 TrivialExtract this avatar block behind
OgImageAvatarprops instead of maintaining a second copy.This now duplicates the same hardcoded source/alt and circular avatar wrapper pattern introduced in
app/components/OgImage/components/OgImageAvatar.vue. The next asset or framing tweak will have to be updated in both places.As per coding guidelines, "No duplication: extract repeated logic, markup patterns, or style combinations into reusable components, composables, or utilities. Refactor existing duplicates on sight."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/OgImage/OgImageHome.vue` around lines 80 - 104, Replace the duplicated avatar markup in OgImageHome.vue with the reusable OgImageAvatar component: remove the hardcoded div/img block and render <OgImageAvatar> passing the same image source and alt (src="/avatar-thorsten-seyschab.jpg", alt="Thorsten Seyschab") and any size/position data as props (e.g., size or diameter=350 and positioning props like top="115px" right="80px" or a wrapperStyle prop) so the circular wrapper, gradient border and objectFit are centralized in OgImageAvatar.vue; ensure the prop names you use match the component's props in OgImageAvatar and update any imports/registration in OgImageHome.vue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/content/TalkCard.vue`:
- Around line 84-92: There are two identical CSS rules for .card-link::after in
TalkCard.vue; remove the duplicate block so only a single .card-link::after rule
remains (keep one instance with the existing declarations: `@apply` absolute
inset-0; content: '';), making sure to remove the second occurrence and run a
quick style check to confirm no other duplicates remain.
In `@app/components/OgImage/components/OgImageFooter.vue`:
- Around line 19-45: The outer container div (the one with inline styles for
display:flex and justifyContent: 'space-between') must include a class name that
contains "flex-" (e.g., "flex-row") so Satori's flex plugin doesn't force
flexDirection: 'column'; add that class to the container and change the two
inner block <div> text elements (the nodes rendering "Thorsten Seyschab" and
"todde.tv") to inline elements such as <span> to avoid triggering the
block-children rule while keeping their existing inline style objects.
In `@app/components/OgImage/OgImageTalk.vue`:
- Around line 17-29: The root div (the outermost container with inline style
width: '1200px', height: '630px', display: 'flex', ...) and the main content
container (the inner container under it that currently uses inline flex styles)
need a CSS class containing the substring "flex-" to prevent nuxt-og-image's
flex plugin from injecting unwanted flexWrap and gap styles; add a class like
"flex-root" on the outermost div and "flex-main" on the main content container
so both elements include a "flex-" prefixed class, leaving existing inline
styles intact.
In `@app/components/ui/AppCard.vue`:
- Line 46: The template applies a non-existent CSS class "app-card-interactive"
alongside "app-card-hover" and "app-card-base"; either add a matching rule for
.app-card-interactive to the component's <style scoped> block (e.g., styles for
interactive state, pointer cursor, focus/active outlines consistent with
.app-card-hover) or remove "app-card-interactive" from the class list in the
template so only defined classes (.app-card-base, .app-card-hover, .app-card)
are used; update the component accordingly (AppCard.vue — template class list
and/or style block).
In `@content.config.ts`:
- Around line 63-67: Change the URL fields in the talks schema to use the same
URL validator as the socials collection: replace the current repoUrl, slidesUrl,
and videoUrl definitions (currently using z.string().optional()) with the URL
validator used elsewhere (i.e., z.url().optional()) so they perform proper URL
format validation and remain consistent with the socials schema.
- Line 57: Replace the loose date validator z.string() on the schema's date
field with z.iso.date() so the 'date' field enforces ISO 8601 (YYYY-MM-DD)
date-only format; locate the schema definition where the property name 'date'
currently uses z.string() and change it to use z.iso.date(), ensuring imports
from Zod remain correct.
In `@content/talks/2024-09-17-pragvue.md`:
- Around line 1-9: Create a GitHub issue to track publishing the talk repository
referenced in content/talks/2024-09-17-pragvue.md: use a clear title like
"Publish PragVue talk repository for Moving Vue to 3D", include the commented
repo URL (https://github.com/toddeTV/talk_2024-09-17_PragVue-conference),
describe the TODO in the file and required next steps (make repo public, add
slides/code, add README and license), assign the owner and set a milestone/label
for publishing.
---
Outside diff comments:
In `@app/components/OgImage/OgImageHome.vue`:
- Around line 80-104: Replace the duplicated avatar markup in OgImageHome.vue
with the reusable OgImageAvatar component: remove the hardcoded div/img block
and render <OgImageAvatar> passing the same image source and alt
(src="/avatar-thorsten-seyschab.jpg", alt="Thorsten Seyschab") and any
size/position data as props (e.g., size or diameter=350 and positioning props
like top="115px" right="80px" or a wrapperStyle prop) so the circular wrapper,
gradient border and objectFit are centralized in OgImageAvatar.vue; ensure the
prop names you use match the component's props in OgImageAvatar and update any
imports/registration in OgImageHome.vue.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Lite
Run ID: 3bcec45c-7cc1-4525-b420-375dd3a5e559
⛔ Files ignored due to path filters (1)
public/avatar-thorsten-seyschab.jpgis excluded by!**/*.jpg
📒 Files selected for processing (31)
.coderabbit.ymlREADME.mdapp/components/OgImage/OgImageHome.vueapp/components/OgImage/OgImageTalk.vueapp/components/OgImage/components/OgImageAvatar.vueapp/components/OgImage/components/OgImageFooter.vueapp/components/OgImage/components/OgImageGlow.vueapp/components/content/TalkCard.vueapp/components/content/TestimonialCard.vueapp/components/content/TestimonialCarousel.vueapp/components/ui/AppCard.vueapp/pages/talks/[slug].vueapp/pages/talks/index.vuecontent.config.tscontent/pages/example.mdcontent/talks/2022-10-28-musiktheorie-symposium.mdcontent/talks/2023-01-25-masters-thesis-defense.mdcontent/talks/2024-09-17-pragvue.mdcontent/talks/2024-11-12-nuxtnation.mdcontent/talks/2025-01-29-vue-js-nation.mdcontent/talks/2025-02-13-hamburg-vue-js-meetup.mdcontent/talks/2025-04-08-dev-day.mdcontent/talks/2025-05-22-printed-europe.mdcontent/talks/2025-06-04-frontend-nation.mdcontent/talks/2025-09-23-prag-vue.mdcontent/talks/2025-10-08-pre-vite-conf.mdcontent/talks/2025-10-10-vite-conf.mdcontent/talks/2025-10-25-vue-fes-japan.mdcontent/talks/2026-03-13-vuejs-amsterdam.mdnuxt.config.tspublic/avatar-thorsten-seyschab.webp
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/content/TalkCard.vue`:
- Around line 15-16: The isUpcoming computed currently uses useTodayDate()
inside TalkCard.vue (isUpcoming = computed(() => props.talk.date >=
today.value)), which bakes a build-time "today" into generated HTML; remove the
runtime useTodayDate dependency and instead derive the upcoming flag at build
time or from persisted metadata: either add an isUpcoming boolean to the talk
data/frontmatter (props.talk.isUpcoming) or accept a build-time
buildDate/buildTimestamp injected by the page and compute isUpcoming in the page
(not inside TalkCard) and pass the resulting boolean into TalkCard (e.g., via an
isUpcoming prop), updating references to isUpcoming and removing useTodayDate
usage.
- Around line 22-25: Add a machine-readable datetime attribute to the <time>
element in TalkCard.vue by setting datetime="{{ talk.date }}" (use the existing
talk.date value, which is already ISO-formatted) so the element becomes
semantically correct and retains metadata; update the <time> tag that currently
displays {{ talk.date }} to include datetime bound to talk.date.
In `@app/components/OgImage/components/OgImageFooter.vue`:
- Around line 10-16: The footer's accent line is absolutely positioned inside
the same wrapper that sizes to the text row (the div with :style containing
position:'absolute', bottom:'60px', left:'60px', right:'60px'), causing the
accent to overlap the footer content; fix by reserving space for the
accent—either add bottom padding to that wrapper (paddingBottom equal to the
accent's height + gap) or move the accent element out of absolute positioning
into normal flow just after the footer content (remove position:'absolute' from
the accent and render it as a block/HR sibling of the text) so the accent
renders below the footer instead of through it.
In `@content.config.ts`:
- Around line 52-54: The talks collection currently uses a glob "talks/**" which
can match nested or non-markdown files; update the defineCollection call for the
talks collection to use the constrained glob "talks/*.md" so it only ingests
top-level markdown talk pages (i.e., change the source value in the talks
defineCollection entry from "talks/**" to "talks/*.md").
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Lite
Run ID: f43c7341-507e-448f-b6e0-059082aeaaeb
📒 Files selected for processing (4)
app/components/OgImage/components/OgImageFooter.vueapp/components/content/TalkCard.vueapp/components/ui/AppCard.vuecontent.config.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/content/TalkCard.vue`:
- Around line 45-72: The repeated NuxtLink blocks in TalkCard.vue (the
Slides/Video/Repo links using talk.slidesUrl, talk.videoUrl, talk.repoUrl and
identical classes) should be collapsed into a single render path: build an array
of resource objects (e.g., { key: 'slides', url: talk.slidesUrl, icon:
'ph:presentation', label: 'Slides' } etc.) in the component data/computed and
replace the three NuxtLink elements with one v-for that conditionally renders
only items with a URL, or extract a small ResourceLink child component that
accepts props (url, icon, label) and use it in a v-for; ensure the link
attributes (target="_blank", :to binding, classes, and Icon usage) are preserved
and accessibility text remains the same.
- Around line 2-12: Extract the inline talk shape used in TalkCard.vue into a
shared exported TypeScript interface (e.g., export interface TalkSummary or
TalkCardTalk) in a central types file or a domain module, replace the inline
generic in defineProps<{ talk: ... }> with defineProps<{ talk: TalkSummary }>
and import that type into TalkCard.vue, and update any other components or page
mappers that currently duplicate the same object shape to import and use the new
TalkSummary/TalkCardTalk type so the contract is single-sourced.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Lite
Run ID: 36bd76eb-839d-45f5-b7c2-e981e870f48c
📒 Files selected for processing (1)
app/components/content/TalkCard.vue
This PR adds a talk overview and detail talk subpages with talk content collection and data for speaking engagements.
Summary by CodeRabbit
New Features
Updates