diff --git a/scripts/sync-sched/schedule-2025.json b/scripts/sync-sched/schedule-2025.json
index 7f8155a483..07eff7b0f2 100644
--- a/scripts/sync-sched/schedule-2025.json
+++ b/scripts/sync-sched/schedule-2025.json
@@ -894,7 +894,7 @@
"event_end": "2025-09-08 14:55",
"event_type": "GraphQL in Production",
"description": "In Booking.com we are heavily using Federated GraphQL approach, more than 150 backend sub-graph services are integrated from different domains of the company such as accommodations, partner, flights, cars, trips, and fintech.\n \n \nOur federated GraphQL layer hosts daily 11b+ incoming requests, Federation in the back distributes 14b+ requests to the sub-graphs per day. We have a diverse set of clients such as Booking traveller, partner native apps/web clients, 140+ SSR (Server Side Rendering) services for Web/Mobile rendering, and AI chatbots. This level of adoption brings unique challenges in terms of security and traffic management. In Booking.com we have a large attack surface since our GraphQL schema is huge, to be specific we have ~7k types with 27k+ fields. In this session, we will share our schema driven approaches to mitigate risks due to authN/Z leaks, DDoS attacks or exposure of sensitive PII/PCI data. These methodologies are designed with a high degree of generality, ensuring their applicability and scalability across every other Federated GraphQL system.",
- "goers": "0",
+ "goers": "1",
"seats": "0",
"invite_only": "N",
"venue": "Studio",
diff --git a/scripts/sync-sched/speakers.json b/scripts/sync-sched/speakers.json
index 7a2d0026a9..de86ebfe74 100644
--- a/scripts/sync-sched/speakers.json
+++ b/scripts/sync-sched/speakers.json
@@ -739,7 +739,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "christian.ernst",
@@ -843,7 +843,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "danielle.man",
@@ -905,11 +905,20 @@
"location": "",
"url": "",
"avatar": "//avatars.sched.co/3/1e/23098735/avatar.jpg.320x320px.jpg?7a3",
- "socialurls": [],
+ "socialurls": [
+ {
+ "service": "Twitter",
+ "url": "https://x.com/dotansimha"
+ },
+ {
+ "service": "LinkedIn",
+ "url": "https://www.linkedin.com/in/dotan-simha-36767b29/"
+ }
+ ],
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "dotansimha",
@@ -944,7 +953,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "eitan15",
@@ -1071,7 +1080,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "fionabronwen",
@@ -1106,7 +1115,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "gabrielschulhof",
@@ -1228,7 +1237,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "hello2358",
@@ -1308,7 +1317,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "jamie855",
@@ -1362,7 +1371,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "jared_cheney.7rad60v",
@@ -1439,11 +1448,16 @@
"location": "",
"url": "",
"avatar": "//avatars.sched.co/3/b5/23098759/avatar.jpg.320x320px.jpg?613",
- "socialurls": [],
+ "socialurls": [
+ {
+ "service": "LinkedIn",
+ "url": "https://linkedin.com/in/jeffdolle"
+ }
+ ],
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "jens63",
@@ -1482,7 +1496,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036922058
},
{
"username": "jesperrasmussen",
@@ -1541,7 +1555,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "jordaneldredge",
@@ -1580,7 +1594,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "juancarlosjr97",
@@ -1604,7 +1618,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "kamilkisiela",
@@ -1634,7 +1648,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "keerthan.ekbote",
@@ -1781,7 +1795,7 @@
"about": "Laurin Quast is a developer that started exploring GraphQL, by leading API development at a start-up. Realizing that there are still many unsolved problems and challenges within the space, he started contributing to famous JavaScript libraries, such as GraphQL Code Generator and Tools. Diving deeper, the transition into becoming a full-time open-source developer at The Guild was inevitable. Currently, he is working on Hive helping teams scale GraphQL across teams and organizations.",
"location": "",
"url": "https://the-guild.dev/",
- "avatar": "//avatars.sched.co/2/a6/18743819/avatar.jpg.320x320px.jpg?705",
+ "avatar": "//avatars.sched.co/2/a6/18743819/avatar.jpg.320x320px.jpg?ebc",
"socialurls": [
{
"service": "Twitter",
@@ -1797,7 +1811,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "ldebruijn",
@@ -1833,7 +1847,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "lee_byron.25krdom6",
@@ -1887,7 +1901,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "lyonwj1",
@@ -1936,7 +1950,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "mail1232",
@@ -1951,7 +1965,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "mansi.mittal",
@@ -2026,7 +2040,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "martinbonnin42",
@@ -2156,7 +2170,7 @@
2023,
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "mgiroux7",
@@ -2206,7 +2220,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "michael.astle",
@@ -2291,7 +2305,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "patrick.arminio",
@@ -2505,7 +2519,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181396120
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "robrichard87",
@@ -2521,7 +2535,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181915102
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "ruben.cagnie",
@@ -2552,7 +2566,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181953774
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "saihaj",
@@ -2614,7 +2628,7 @@
"position": "Senior Software Engineer 2",
"name": "Sanver Tarmur",
"about": "Sanver is a Senior Software Engineer II at Booking.com with 15 years of industry experience. In recent years, he has been leading the Federated GraphQL transformation at Booking.com, focusing on scaling, enhancing the security of the GraphQL platform, and improving the developer experience for internal Graph users.",
- "location": "",
+ "location": "Amsterdam",
"url": "",
"avatar": "//avatars.sched.co/0/9e/23098798/avatar.jpg.320x320px.jpg?318",
"socialurls": [],
@@ -2856,7 +2870,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181966765
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "stefan239",
@@ -3142,7 +3156,7 @@
2024,
2025
],
- "~syncedDetailsAt": 1750181973559
+ "~syncedDetailsAt": 1751036945383
},
{
"username": "vincent.desmares",
@@ -3192,7 +3206,7 @@
"_years": [
2025
],
- "~syncedDetailsAt": 1750181396119
+ "~syncedDetailsAt": 1751036941512
},
{
"username": "watson17",
diff --git a/src/app/conf/2025/schedule/[id]/page.tsx b/src/app/conf/2025/schedule/[id]/page.tsx
index a7a2a928c6..c64649d3c6 100644
--- a/src/app/conf/2025/schedule/[id]/page.tsx
+++ b/src/app/conf/2025/schedule/[id]/page.tsx
@@ -22,6 +22,7 @@ import { CtaCardSection } from "../../components/cta-card-section"
import { Button } from "@/app/conf/_design-system/button"
import { SessionTags } from "../../components/session-tags"
import { formatDescription } from "./format-description"
+import { formatBlockTime } from "../_components/format-block-time"
type SessionProps = { params: { id: string } }
@@ -194,10 +195,15 @@ function SessionHeader({
diff --git a/src/app/conf/2025/schedule/_components/format-block-time.tsx b/src/app/conf/2025/schedule/_components/format-block-time.tsx
new file mode 100644
index 0000000000..135278e43e
--- /dev/null
+++ b/src/app/conf/2025/schedule/_components/format-block-time.tsx
@@ -0,0 +1,13 @@
+import { parseISO } from "date-fns"
+
+const timeFormat = new Intl.DateTimeFormat(undefined, {
+ hour: "2-digit",
+ minute: "2-digit",
+})
+export const formatBlockTime = (start: string, end?: Date) => {
+ const startDate = parseISO(start)
+ if (end) {
+ return timeFormat.formatRange(startDate, end).replace("AM –", "–")
+ }
+ return timeFormat.format(startDate)
+}
diff --git a/src/app/conf/2025/schedule/_components/schedule-list.tsx b/src/app/conf/2025/schedule/_components/schedule-list.tsx
index e2fedb469b..24aacce712 100644
--- a/src/app/conf/2025/schedule/_components/schedule-list.tsx
+++ b/src/app/conf/2025/schedule/_components/schedule-list.tsx
@@ -16,6 +16,7 @@ import {
FilterStates,
ResetFiltersButton,
} from "./filters"
+import { formatBlockTime } from "./format-block-time"
export interface FiltersConfig
extends Partial<
@@ -101,19 +102,6 @@ function getSessionsByDay(
}
}
-const timeFormat = new Intl.DateTimeFormat(undefined, {
- hour: "2-digit",
- minute: "2-digit",
-})
-const formatBlockTime = (start: string, end?: string) => {
- const startDate = parseISO(start)
- if (end) {
- const endDate = parseISO(end)
- return timeFormat.formatRange(startDate, endDate)
- }
- return timeFormat.format(startDate)
-}
-
export interface ScheduleListProps {
showFilter?: boolean
scheduleData: ScheduleSession[]
@@ -205,8 +193,18 @@ export function ScheduleList({
{Object.entries(concurrentSessionsGroup).map(
([sessionDate, sessions], i, blocks) => {
- const blockEnd = sessions[0]?.event_end
- const nextBlockStart = blocks[i + 1]?.[0]
+ const blockEnd = new Date(
+ Math.max(
+ ...sessions.map(session =>
+ new Date(session.event_end).getTime(),
+ ),
+ ),
+ )
+
+ const nextBlock = blocks[i + 1]
+ const nextBlockStart = nextBlock?.[0]
+ ? new Date(nextBlock[0])
+ : undefined
const isBreak =
sessions[0]?.event_type
@@ -216,7 +214,9 @@ export function ScheduleList({
?.toLowerCase()
.includes("break")
const hasDashedBorder =
- blockEnd && blockEnd === nextBlockStart && !isBreak
+ blockEnd &&
+ blockEnd.getTime() === nextBlockStart?.getTime() &&
+ !isBreak
return (
-
+
{formatBlockTime(sessionDate, blockEnd)}
@@ -236,6 +236,7 @@ export function ScheduleList({
session={session}
year={year}
eventsColors={eventsColors}
+ blockEnd={blockEnd}
/>
))}
diff --git a/src/app/conf/2025/schedule/_components/schedule-session-card.tsx b/src/app/conf/2025/schedule/_components/schedule-session-card.tsx
index 7a77fac0e7..4416196a73 100644
--- a/src/app/conf/2025/schedule/_components/schedule-session-card.tsx
+++ b/src/app/conf/2025/schedule/_components/schedule-session-card.tsx
@@ -1,12 +1,23 @@
-import React from "react"
+import React, { Fragment } from "react"
+import { ics, google, outlook, CalendarEvent } from "calendar-link"
+import { clsx } from "clsx"
+import {
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuItems,
+ Transition,
+} from "@headlessui/react"
import { SchedSpeaker, ScheduleSession } from "@/app/conf/_api/sched-types"
import { Anchor } from "@/app/conf/_design-system/anchor"
import { Tag } from "@/app/conf/_design-system/tag"
import { PinIcon } from "@/app/conf/_design-system/pixelarticons/pin-icon"
+import ClockIcon from "@/app/conf/_design-system/pixelarticons/clock.svg?svgr"
import { getEventTitle } from "../../utils"
+import { CalendarIcon } from "@/app/conf/_design-system/pixelarticons/calendar-icon"
function isString(x: unknown): x is string {
return Object.prototype.toString.call(x) === "[object String]"
@@ -16,10 +27,12 @@ export function ScheduleSessionCard({
session,
year,
eventsColors,
+ blockEnd,
}: {
session: ScheduleSession
year: `202${number}`
eventsColors: Record
+ blockEnd: Date
}) {
let eventType = session.event_type
@@ -40,17 +53,43 @@ export function ScheduleSessionCard({
const eventColor = eventsColors[session.event_type]
+ let blockTimeFraction = 1
+ if (blockEnd.getTime() !== new Date(session.event_end).getTime()) {
+ blockTimeFraction =
+ (new Date(session.event_end).getTime() -
+ new Date(session.event_start).getTime()) /
+ (blockEnd.getTime() - new Date(session.event_start).getTime())
+ }
+
return session.event_type === "Breaks" ? (
{eventTitle}
) : (
-
+
a:hover)]:[--bg:hsl(var(--color-neu-0)/.9)] dark:[&:has(>a:hover)]:[--bg:hsl(var(--color-neu-0)/.8)]",
+ "group relative size-full p-4 font-normal no-underline ring-neu-400 focus-visible:z-[1] dark:ring-neu-100 [&:has(>a:hover)]:ring-1",
+ blockTimeFraction < 1 && "[--bg:hsl(var(--color-neu-0)/50)]",
+ )}
+ style={
+ {
+ "--time": `${blockTimeFraction * 100}%`,
+ background:
+ blockTimeFraction < 1
+ ? `linear-gradient(to bottom, var(--bg), var(--bg) var(--time), hsl(var(--color-neu-0)/.8) var(--time), hsl(var(--color-neu-0)/.8))`
+ : "var(--bg)",
+ } as {}
+ }
+ >
s.name).join(", ")}`}
+ aria-label={`Read more about "${eventTitle}" by ${speakers
+ .map(s => s.name)
+ .join(", ")}`}
/>
{eventType && (
@@ -83,9 +122,28 @@ export function ScheduleSessionCard({
))}
)}
-
-
- {session.venue}
+
+
+
+ {session.venue}
+
+ {blockTimeFraction < 1 && (
+
+
+ {Math.round(
+ (new Date(session.event_end).getTime() -
+ new Date(session.event_start).getTime()) /
+ (1000 * 60),
+ )}{" "}
+ min
+
+ )}
+
@@ -93,3 +151,82 @@ export function ScheduleSessionCard({
)
}
+
+function AddToCalendarLink({
+ eventTitle,
+ session,
+ speakers,
+ className,
+}: {
+ eventTitle: string
+ session: ScheduleSession
+ speakers: SchedSpeaker[]
+ className?: string
+}) {
+ const calendarEvent: CalendarEvent = {
+ title: eventTitle,
+ start: session.event_start,
+ end: session.event_end,
+ description: session.description,
+ location: session.venue,
+ organizer: {
+ name: `GraphQLConf ${new Date().getFullYear()}`,
+ email: "graphql_events@linuxfoundation.org",
+ },
+ guests: speakers.map(s => s.name),
+ }
+
+ const calendars = {
+ ICS: ics,
+ Google: google,
+ Outlook: outlook,
+ }
+
+ return (
+
+ )
+}
diff --git a/src/app/conf/2025/speakers/[id]/long-session-card.tsx b/src/app/conf/2025/speakers/[id]/long-session-card.tsx
index 32ba47f9fd..6b046b9da2 100644
--- a/src/app/conf/2025/speakers/[id]/long-session-card.tsx
+++ b/src/app/conf/2025/speakers/[id]/long-session-card.tsx
@@ -1,5 +1,5 @@
import { clsx } from "clsx"
-import { ics } from "calendar-link"
+import { CalendarEvent, google, ics, outlook } from "calendar-link"
import { SchedSpeaker, ScheduleSession } from "@/app/conf/2023/types"
import { Button } from "@/app/conf/_design-system/button"
@@ -11,8 +11,10 @@ import PlusIcon from "@/app/conf/_design-system/pixelarticons/plus.svg?svgr"
import PlayIcon from "@/app/conf/_design-system/pixelarticons/play.svg?svgr"
import { findVideo } from "../../schedule/[id]/session-video"
import { getEventTitle } from "../../utils"
-import React from "react"
+import React, { Fragment } from "react"
import { SessionTags } from "../../components/session-tags"
+import { Menu, MenuItem, MenuItems, Transition } from "@headlessui/react"
+import { MenuButton } from "@headlessui/react"
export interface LongSessionCardProps
extends React.HTMLAttributes
{
@@ -159,29 +161,71 @@ function AddToCalendarLink({
eventTitle,
session,
speakers,
+ className,
}: {
eventTitle: string
session: ScheduleSession
speakers: SchedSpeaker[]
+ className?: string
}) {
+ const calendarEvent: CalendarEvent = {
+ title: eventTitle,
+ start: session.event_start,
+ end: session.event_end,
+ description: session.description,
+ location: session.venue,
+ organizer: {
+ name: `GraphQLConf ${new Date().getFullYear()}`,
+ email: "graphql_events@linuxfoundation.org",
+ },
+ guests: speakers.map(s => s.name),
+ }
+
+ const calendars = {
+ ICS: ics,
+ Google: google,
+ Outlook: outlook,
+ }
+
return (
- s.name),
- })}
- >
-
- Add to calendar
-
+
)
}
diff --git a/src/app/conf/_design-system/pixelarticons/clock.svg b/src/app/conf/_design-system/pixelarticons/clock.svg
index 9f0e6b1f9a..bfc82287a8 100644
--- a/src/app/conf/_design-system/pixelarticons/clock.svg
+++ b/src/app/conf/_design-system/pixelarticons/clock.svg
@@ -3,10 +3,9 @@
width="16"
height="16"
viewBox="0 0 16 16"
- fill="none"
+ fill="currentColor"
>
diff --git a/src/globals.css b/src/globals.css
index 8d33d859df..ac04ce6d8d 100644
--- a/src/globals.css
+++ b/src/globals.css
@@ -533,6 +533,7 @@ div[id^="headlessui-menu-items"] {
}
/* without this Headless UI breaks sticky navbar */
-html:has([role="listbox"][data-open]) {
+html:has([role="listbox"][data-open]),
+html:has([role="menu"][data-open]) {
overflow: visible !important;
}