From 00126523ce75363605ee99d18df827e0c81106ac Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 21:07:06 +0200 Subject: [PATCH 01/18] Link Singapore page to schedule, remove ticket and CFP buttons --- src/app/day/2026/singapore/page.tsx | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/app/day/2026/singapore/page.tsx b/src/app/day/2026/singapore/page.tsx index 619ab1ee47..a3c36e1fff 100644 --- a/src/app/day/2026/singapore/page.tsx +++ b/src/app/day/2026/singapore/page.tsx @@ -4,10 +4,6 @@ import { Button } from "@/app/conf/_design-system/button" import { Hero, HeroDateAndLocation } from "../components/hero" import { AboutSection } from "../components/about-section" import { WhyAttendSection } from "../components/why-attend-section" -import { - BecomeASpeakerSection, - CfpButton, -} from "../components/become-a-speaker" import { EventPartnersSection } from "../components/event-partners" import { CtaCardSection } from "../components/cta-card-section" import { MarqueeRows } from "@/app/conf/2026/components/marquee-rows" @@ -15,8 +11,8 @@ import { PastSpeakersSection } from "../components/past-speakers" import { NavbarPlaceholder } from "../components/navbar" import { GallerySection } from "../../gallery-section" -const TICKET_LINK = - "https://portal.joinfost.io/event/future-of-software-technologies-singapore-2026/9521470b-6661-4c85-8594-b74d9d7cf2e3/graphql-day-at-fost-singapore" +const SCHEDULE_LINK = + "https://portal.joinfost.io/event/future-of-software-technologies-singapore-2026/9521470b-6661-4c85-8594-b74d9d7cf2e3/graphql-day-at-fost-singapore#tab-content-program" const MARQUEE_ITEMS = [ ["SINGAPORE", "APRIL 2026", "GRAPHQL DAY", "FOST", "COMMUNITY", "APIs"], @@ -46,10 +42,9 @@ export default function SingaporePage() { location="Singapore" />
- -
@@ -60,20 +55,19 @@ export default function SingaporePage() { />
- Date: Tue, 28 Apr 2026 21:44:37 +0200 Subject: [PATCH 02/18] Embed FOST schedule on the Singapore page --- src/app/day/2026/singapore/page.tsx | 12 +- src/app/day/2026/singapore/schedule-data.ts | 94 +++++++ .../day/2026/singapore/schedule-section.tsx | 266 ++++++++++++++++++ .../singapore/speakers/akshat-sharma.webp | Bin 0 -> 20682 bytes .../singapore/speakers/michael-staib.webp | Bin 0 -> 8100 bytes .../2026/singapore/speakers/pascal-senn.webp | Bin 0 -> 5976 bytes src/app/day/layout.tsx | 4 +- 7 files changed, 370 insertions(+), 6 deletions(-) create mode 100644 src/app/day/2026/singapore/schedule-data.ts create mode 100644 src/app/day/2026/singapore/schedule-section.tsx create mode 100644 src/app/day/2026/singapore/speakers/akshat-sharma.webp create mode 100644 src/app/day/2026/singapore/speakers/michael-staib.webp create mode 100644 src/app/day/2026/singapore/speakers/pascal-senn.webp diff --git a/src/app/day/2026/singapore/page.tsx b/src/app/day/2026/singapore/page.tsx index a3c36e1fff..42e3cf607b 100644 --- a/src/app/day/2026/singapore/page.tsx +++ b/src/app/day/2026/singapore/page.tsx @@ -10,9 +10,9 @@ import { MarqueeRows } from "@/app/conf/2026/components/marquee-rows" import { PastSpeakersSection } from "../components/past-speakers" import { NavbarPlaceholder } from "../components/navbar" import { GallerySection } from "../../gallery-section" +import { ScheduleSection } from "./schedule-section" -const SCHEDULE_LINK = - "https://portal.joinfost.io/event/future-of-software-technologies-singapore-2026/9521470b-6661-4c85-8594-b74d9d7cf2e3/graphql-day-at-fost-singapore#tab-content-program" +const SCHEDULE_ANCHOR = "#schedule" const MARQUEE_ITEMS = [ ["SINGAPORE", "APRIL 2026", "GRAPHQL DAY", "FOST", "COMMUNITY", "APIs"], @@ -42,7 +42,10 @@ export default function SingaporePage() { location="Singapore" />
-
@@ -54,6 +57,7 @@ export default function SingaporePage() { items={MARQUEE_ITEMS} />
+ @@ -63,7 +67,7 @@ export default function SingaporePage() { description="Catch up on the talks, descriptions, and speakers from GraphQL Day @ FOST Singapore." > - + () - for (const session of singaporeSessions) { - const day = session.start.slice(0, 10) - const list = sessionsByDay.get(day) ?? [] - list.push(session) - sessionsByDay.set(day, list) - } - return (
- {Array.from(sessionsByDay.entries()).map(([day, sessions], dayIdx) => ( -
-
-

- {format(parseISO(day), "EEEE, MMMM d, yyyy")} -

- {sessions.map((session, i) => ( - - ))} -
+ {singaporeSessions.map((session, i) => ( + ))}
@@ -78,25 +57,21 @@ function SessionBlock({ }) { return (
-
+
{session.description && ( - <> -
- - +
)} {session.speakers.length > 0 && ( - <> -
-

- Session {session.speakers.length === 1 ? "speaker" : "speakers"} -

- - + )}
) @@ -146,20 +121,6 @@ function SessionHeader({ ) } -function SessionDescription({ session }: { session: SingaporeSession }) { - return ( -
-

Session description

-
-
- ) -} - function SessionSpeakers({ speakers, className, From 2c91d5caad54eb5a6d7e9a5f7d0363deb2eb4ec6 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:00:50 +0200 Subject: [PATCH 04/18] Derive topic tags, add speaker socials, drop title-row name --- src/app/day/2026/singapore/schedule-data.ts | 40 +++++++++-- .../day/2026/singapore/schedule-section.tsx | 66 +++++++++++++++---- 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/app/day/2026/singapore/schedule-data.ts b/src/app/day/2026/singapore/schedule-data.ts index 5a678483e3..1ada9a56d8 100644 --- a/src/app/day/2026/singapore/schedule-data.ts +++ b/src/app/day/2026/singapore/schedule-data.ts @@ -10,6 +10,7 @@ export interface SingaporeSpeaker { company: string jobtitle: string avatar: StaticImageData + socialurls: { service: string; url: string }[] } export interface SingaporeSession { @@ -20,7 +21,8 @@ export interface SingaporeSession { start: string /** ISO 8601 in venue local time, Asia/Singapore */ end: string - type: string + /** Topic tags derived from the session description. */ + tags: string[] /** HTML */ description: string venue: string @@ -29,6 +31,18 @@ export interface SingaporeSession { export const SINGAPORE_TIMEZONE = "Asia/Singapore" +/** Color per topic, picked to read clearly against the cream/dark backgrounds. */ +export const tagColors: Record = { + Security: "#CC6BB0", + "Zero Trust": "#894545", + "Service Mesh": "#36C1A0", + "AI Agents": "#7e66cc", + Federation: "#FC8251", + "Public Sector": "#4e6e82", + "Schema Evolution": "#cbc749", + Observability: "#1a5b77", +} + export const singaporeSessions: SingaporeSession[] = [ { id: 3224, @@ -36,7 +50,7 @@ export const singaporeSessions: SingaporeSession[] = [ title: "Securing GraphQL at Scale with Zero Trust APIs", start: "2026-04-15T15:55:00+08:00", end: "2026-04-15T16:20:00+08:00", - type: "Talk", + tags: ["Security", "Zero Trust", "Service Mesh"], description: "

Modern microservice architectures often decentralize authentication and authorization, leading to inconsistent security policies and increased attack surfaces. GraphQL, while powerful, introduces unique risks such as over-fetching, query batching abuse, and introspection-based attacks that many teams underestimate.

\n

In this talk, I present a real-world case study of building a secure, identity-aware GraphQL gateway that enforces Zero Trust principles across distributed services. By integrating centralized identity management with Keycloak and leveraging service mesh technologies like Istio and Envoy, we created a unified layer for authentication, authorization, and traffic governance.

\n

The session will walk through practical challenges, including enforcing fine-grained access control, implementing query cost analysis, and mitigating abuse patterns in production. Attendees will gain insights into designing secure GraphQL APIs that scale without compromising performance or developer experience.

\n

This talk matters because API security is no longer optional. As organizations increasingly adopt GraphQL, understanding how to secure it in real-world systems is critical to preventing data leaks and maintaining trust.

\n", venue: "Stage 3", @@ -47,6 +61,12 @@ export const singaporeSessions: SingaporeSession[] = [ company: "Deskree", jobtitle: "Technology Advocate", avatar: akshatSharmaAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/akshat-sharma11", + }, + ], }, ], }, @@ -56,7 +76,7 @@ export const singaporeSessions: SingaporeSession[] = [ title: "GraphQL as the Execution Layer for AI Agents", start: "2026-04-15T16:20:00+08:00", end: "2026-04-15T16:45:00+08:00", - type: "Talk", + tags: ["AI Agents", "Federation", "Public Sector"], description: "

Your next million API consumers won't be developers. They'll be AI agents. And they don't read documentation, parse hypermedia links, or guess which of your 200 REST endpoints returns the data they need.

\n

This talk examines what happens when autonomous AI agents become the primary consumers of your API layer. Drawing on real data from Singapore's public government APIs, I'll show how REST responses waste 30–60% of an agent's token budget on structural overhead, and how a typed, self-describing schema changes the equation entirely.

\n

We'll walk through the three properties that make an API truly agent-native: discoverability, precision, and composability. We'll look at what it would take to unify API estates like Singapore's 3,000+ government APIs across 75+ agencies into a single, self-describing surface. A pattern Gartner expects 30% of enterprises to adopt by 2027.

\n

You'll leave with a framework for what makes an API truly agent-native, why GraphQL's type system and federation model get you there, and how to start without a rewrite.

\n", venue: "Stage 3", @@ -67,6 +87,12 @@ export const singaporeSessions: SingaporeSession[] = [ company: "ChilliCream", jobtitle: "Founder", avatar: pascalSennAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/pascal-senn-90899a15a", + }, + ], }, ], }, @@ -77,7 +103,7 @@ export const singaporeSessions: SingaporeSession[] = [ "Closing the Loop: How GraphQL Gives Coding Agents Eyes on What Actually Matters", start: "2026-04-15T16:45:00+08:00", end: "2026-04-15T17:10:00+08:00", - type: "Talk", + tags: ["AI Agents", "Schema Evolution", "Observability"], description: "

Coding agents are reshaping how we build software. Implementing features, refactoring systems, and shipping changes at a pace unthinkable 6 months ago. But to be successful with agents you need the right feedback loop. One that guides your agent to success, not into the spiral of death.

\n

Ask Claude to add a review system to your product API. Without knowing what's in use, it might reshape your types, move fields, and break your deployed clients because it is missing a crucial feedback loop of what's in use in your clients.

\n

GraphQL changes this. Every client operation explicitly declares the exact fields and types it needs. That gives you something rare: field-level usage data across your entire consumer base. Not endpoint hits, but actual demand, broken down to the individual field.

\n

When coding agents can access this data, they stop guessing. Evolve your schema grounded in reality, not assumptions.

\n

This talk shows how GraphQL's inherent usage visibility and the rise of coding agents create a feedback loop that didn't exist before. And why it matters for anyone building APIs that need to evolve fast.

\n", venue: "Stage 3", @@ -88,6 +114,12 @@ export const singaporeSessions: SingaporeSession[] = [ company: "ChilliCream", jobtitle: "Founder", avatar: michaelStaibAvatar, + socialurls: [ + { + service: "linkedin", + url: "https://www.linkedin.com/in/michael-staib-31519571/", + }, + ], }, ], }, diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index 77053107a7..dad0205c83 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -5,12 +5,17 @@ import { Tag } from "@/app/conf/_design-system/tag" import { CalendarIcon } from "@/app/conf/_design-system/pixelarticons/calendar-icon" import { PinIcon } from "@/app/conf/_design-system/pixelarticons/pin-icon" import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" +import { + SocialIcon, + SocialIconType, +} from "@/app/conf/_design-system/social-icon" import { formatDescription } from "@/app/conf/2026/schedule/[id]/format-description" import { SingaporeSession, SingaporeSpeaker, singaporeSessions, + tagColors, } from "./schedule-data" const TIME_RANGE = new Intl.DateTimeFormat("en-US", { @@ -89,16 +94,8 @@ function SessionHeader({ return (
-

- {session.speakers.map((s, i) => ( - - {s.name} - {i !== session.speakers.length - 1 && , } - - ))} -

-

{session.title}

-
+

{session.title}

+
@@ -115,7 +112,18 @@ function SessionHeader({
)}
- {session.type} + {session.tags.length > 0 && ( +
+ {session.tags.map(tag => ( + + {tag} + + ))} +
+ )}
) @@ -200,7 +208,7 @@ function SpeakerCard({
-
+
{speaker.name}
{subtitle && ( @@ -209,12 +217,46 @@ function SpeakerCard({

)}
+ {speaker.socialurls.length > 0 && ( + + )}
) } +function SpeakerSocialLinks({ + links, +}: { + links: SingaporeSpeaker["socialurls"] +}) { + const ordered = SocialIconType.all + .map(service => + links.find(l => l.service.toLowerCase() === service.toLowerCase()), + ) + .filter((x): x is { service: string; url: string } => !!x?.url) + + if (ordered.length === 0) return null + + return ( +
+ {ordered.map(social => ( + + + + ))} +
+ ) +} + function Hr({ className }: { className?: string }) { return (
Date: Tue, 28 Apr 2026 22:08:51 +0200 Subject: [PATCH 05/18] Tone the schedule strip, add GitHub social and ChilliCream website links --- src/app/conf/_design-system/social-icon.tsx | 7 +++++++ src/app/day/2026/singapore/schedule-data.ts | 4 ++++ src/app/day/2026/singapore/schedule-section.tsx | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/conf/_design-system/social-icon.tsx b/src/app/conf/_design-system/social-icon.tsx index 1cd3c83ea9..4f4d63a397 100644 --- a/src/app/conf/_design-system/social-icon.tsx +++ b/src/app/conf/_design-system/social-icon.tsx @@ -4,6 +4,7 @@ import { LinkedInFilledIcon, InstagramIcon, GlobeIcon, + GitHubIcon, } from "@/icons" export type SocialIconType = @@ -11,6 +12,7 @@ export type SocialIconType = | "linkedin" | "facebook" | "instagram" + | "github" | "website" export const SocialIconType = { @@ -22,6 +24,7 @@ export const SocialIconType = { all: [ "linkedin", "twitter", + "github", "instagram", "facebook", "website", @@ -42,6 +45,8 @@ export const SocialIcon = ({ type, ...rest }: SocialIconProps) => { return case "instagram": return + case "github": + return case "website": return default: @@ -59,6 +64,8 @@ export function urlForUser(type: SocialIconType, handleOrWebsite: string) { return `https://www.instagram.com/${handleOrWebsite}` case "facebook": return `https://www.facebook.com/${handleOrWebsite}` + case "github": + return `https://github.com/${handleOrWebsite}` case "website": return handleOrWebsite default: diff --git a/src/app/day/2026/singapore/schedule-data.ts b/src/app/day/2026/singapore/schedule-data.ts index 1ada9a56d8..a96a23e8f2 100644 --- a/src/app/day/2026/singapore/schedule-data.ts +++ b/src/app/day/2026/singapore/schedule-data.ts @@ -92,6 +92,8 @@ export const singaporeSessions: SingaporeSession[] = [ service: "linkedin", url: "https://www.linkedin.com/in/pascal-senn-90899a15a", }, + { service: "github", url: "https://github.com/PascalSenn" }, + { service: "website", url: "https://chillicream.com" }, ], }, ], @@ -119,6 +121,8 @@ export const singaporeSessions: SingaporeSession[] = [ service: "linkedin", url: "https://www.linkedin.com/in/michael-staib-31519571/", }, + { service: "github", url: "https://github.com/michaelstaib" }, + { service: "website", url: "https://chillicream.com" }, ], }, ], diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index dad0205c83..b7d4befc6f 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,9 +35,9 @@ export function ScheduleSection() { return (
-
+

Schedule

From 4b041b4892bb4b41bfed3d22e80a0814ad0665bd Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:09:30 +0200 Subject: [PATCH 06/18] Move the schedule's bg-neu-50 up to the section element --- src/app/day/2026/singapore/schedule-section.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index b7d4befc6f..da2844aa77 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,9 +35,9 @@ export function ScheduleSection() { return (

-
+

Schedule

From 62835f061122a7fc37c0b25de762a673bf1b7eef Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:10:14 +0200 Subject: [PATCH 07/18] Move the schedule's borders to the section element --- src/app/day/2026/singapore/schedule-section.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index da2844aa77..8eccb78dba 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,9 +35,9 @@ export function ScheduleSection() { return (

-
+

Schedule

From c11ec29a507e9b452e653eeca0769fe98c721437 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:10:31 +0200 Subject: [PATCH 08/18] Drop the top border on speaker cards --- src/app/day/2026/singapore/schedule-section.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index 8eccb78dba..a53ec45113 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -181,7 +181,7 @@ function SpeakerCard({ .join(", ") return ( -

+
From 0d2bfb0a53688117ecaac22cedf879a8ffae0da5 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:12:22 +0200 Subject: [PATCH 09/18] Restore
separators and move border-x inside the schedule --- .../day/2026/singapore/schedule-section.tsx | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index a53ec45113..70bdf654e8 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,9 +35,9 @@ export function ScheduleSection() { return (
-
+

Schedule

@@ -65,18 +65,24 @@ function SessionBlock({


{session.description && ( -
+ <> +
+
+ )} {session.speakers.length > 0 && ( - + <> +
+ + )}
) From 57437e7d12f4ed9b105c8423a1aca261365fafea Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:14:27 +0200 Subject: [PATCH 10/18] Lift the schedule and closing marquee out of gql-container --- src/app/day/2026/singapore/page.tsx | 12 ++++---- .../day/2026/singapore/schedule-section.tsx | 30 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/app/day/2026/singapore/page.tsx b/src/app/day/2026/singapore/page.tsx index 9ba5339756..890da9efb5 100644 --- a/src/app/day/2026/singapore/page.tsx +++ b/src/app/day/2026/singapore/page.tsx @@ -60,13 +60,13 @@ export default function SingaporePage() { - -
+ + ) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index 70bdf654e8..fd56e19ec9 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,19 +35,27 @@ export function ScheduleSection() { return (
-
-
-

Schedule

-

- All times in Singapore Time (SGT, UTC+8) -

-
+
+
+
+
+

Schedule

+

+ All times in Singapore Time (SGT, UTC+8) +

+
- {singaporeSessions.map((session, i) => ( - - ))} + {singaporeSessions.map((session, i) => ( + + ))} +
+
) From f497d05923effd5ebace0fc0f24372c380e029d9 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:14:55 +0200 Subject: [PATCH 11/18] Soften the schedule's dark background to bg-neu-50/25 --- src/app/day/2026/singapore/schedule-section.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index fd56e19ec9..fd4fcd0a09 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,7 +35,7 @@ export function ScheduleSection() { return (
From 3b46df622289b25989c9bec2fae39dc979691c65 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:15:09 +0200 Subject: [PATCH 12/18] Make the speaker card backgrounds transparent --- src/app/day/2026/singapore/schedule-section.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index fd4fcd0a09..992947a719 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -195,7 +195,7 @@ function SpeakerCard({ .join(", ") return ( -
+
From 636e14433919a27e632b7f453e3cca8e12e9193e Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:16:20 +0200 Subject: [PATCH 13/18] Clip the bleed
separators with overflow-hidden --- src/app/day/2026/singapore/schedule-section.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index 992947a719..cbc0036831 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -35,7 +35,7 @@ export function ScheduleSection() { return (
From d60d07695a2e77c5830cd317dcb04848b0d71dd8 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 22:34:45 +0200 Subject: [PATCH 14/18] Float the speaker card into the description on xl+ --- .../day/2026/singapore/schedule-section.tsx | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index cbc0036831..e288e871a3 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -68,6 +68,12 @@ function SessionBlock({ session: SingaporeSession isFirst: boolean }) { + // On xl+ with a single speaker we float the card into the bottom-right of + // the description so prose flows around it. Multi-speaker sessions keep + // the regular "speakers below" layout at every breakpoint. + const floatSpeaker = + session.speakers.length === 1 ? session.speakers[0] : null + return (

@@ -75,27 +81,67 @@ function SessionBlock({ {session.description && ( <>
-
)} {session.speakers.length > 0 && ( - <> +

- +
)}
) } +function SessionDescription({ + description, + floatSpeaker, +}: { + description: string + floatSpeaker: SingaporeSpeaker | null +}) { + const paragraphs = parseParagraphs(description) + + return ( +
+ {floatSpeaker && ( +
+ +
+ )} + {paragraphs.map((html, i) => ( +

+ ))} +

+ ) +} + +/** + * Split FOST description HTML (a sequence of `

...

` blocks) into the + * inner HTML of each paragraph so we can render them as real React `

` + * siblings — needed because `shape-outside` only takes effect when the float + * shares a block formatting context with the surrounding text. + */ +function parseParagraphs(html: string): string[] { + const formatted = formatDescription(html) + const matches = formatted.match(/

[\s\S]*?<\/p>/g) + if (!matches) return [formatted] + return matches.map(p => p.replace(/^

/, "").replace(/<\/p>$/, "")) +} + function SessionHeader({ session, className, From c23caf43d38c9543c0c3d14e4f2db2bed16f3ec5 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 23:19:26 +0200 Subject: [PATCH 15/18] Slot the speaker card into the description via flex on xl+ --- src/app/day/2026/singapore/page.tsx | 2 +- .../day/2026/singapore/schedule-section.tsx | 63 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/app/day/2026/singapore/page.tsx b/src/app/day/2026/singapore/page.tsx index 890da9efb5..e1dd72d221 100644 --- a/src/app/day/2026/singapore/page.tsx +++ b/src/app/day/2026/singapore/page.tsx @@ -64,7 +64,7 @@ export default function SingaporePage() { diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index e288e871a3..7b1c0842f1 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -68,27 +68,31 @@ function SessionBlock({ session: SingaporeSession isFirst: boolean }) { - // On xl+ with a single speaker we float the card into the bottom-right of - // the description so prose flows around it. Multi-speaker sessions keep - // the regular "speakers below" layout at every breakpoint. - const floatSpeaker = + // On xl+ with a single speaker we slot the card next to the last two + // paragraphs of the description so it sits in the bottom-right corner. + // Multi-speaker sessions keep the regular "speakers below" layout. + const sideSpeaker = session.speakers.length === 1 ? session.speakers[0] : null return (

-
- +
+ {session.description && ( <> -
+
)} {session.speakers.length > 0 && ( -
+

= 2 + ? paragraphs.length - 2 + : paragraphs.length + const lead = paragraphs.slice(0, splitAt) + const tail = paragraphs.slice(splitAt) return (
- {floatSpeaker && ( -
- + {lead.map((html, i) => ( +

+ ))} + {tail.length > 0 && ( +

+
+ {tail.map((html, i) => ( +

+ ))} +

+ {sideSpeaker && ( +
+ +
+ )}
)} - {paragraphs.map((html, i) => ( -

- ))}

) } @@ -132,8 +145,8 @@ function SessionDescription({ /** * Split FOST description HTML (a sequence of `

...

` blocks) into the * inner HTML of each paragraph so we can render them as real React `

` - * siblings — needed because `shape-outside` only takes effect when the float - * shares a block formatting context with the surrounding text. + * siblings — needed so we can splice the speaker card in alongside the last + * couple of paragraphs at xl+. */ function parseParagraphs(html: string): string[] { const formatted = formatDescription(html) From e714aa823a7dd9b6369d35b9d305b658e4eda3ca Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 23:32:29 +0200 Subject: [PATCH 16/18] Run prettier --- src/app/day/2026/singapore/schedule-section.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index 7b1c0842f1..a08e88b9f3 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -71,20 +71,17 @@ function SessionBlock({ // On xl+ with a single speaker we slot the card next to the last two // paragraphs of the description so it sits in the bottom-right corner. // Multi-speaker sessions keep the regular "speakers below" layout. - const sideSpeaker = - session.speakers.length === 1 ? session.speakers[0] : null + const sideSpeaker = session.speakers.length === 1 ? session.speakers[0] : null return (


{session.description && ( <> -
+
0 && (
-
+
{tail.map((html, i) => (

))} From 0de923facab7223ecceadd74451df5e9e1eaf181 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 23:58:04 +0200 Subject: [PATCH 17/18] Point Day header link from Singapore to NYC --- src/app/day/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/day/layout.tsx b/src/app/day/layout.tsx index 2b316d4dab..84934d035f 100644 --- a/src/app/day/layout.tsx +++ b/src/app/day/layout.tsx @@ -30,8 +30,8 @@ export default function DayLayout({ { children: "All GraphQL Events", href: "/community/events/" }, { children: "GraphQLConf", href: "/conf/2026" }, { - children: "GraphQL Day Singapore", - href: "/day/2026/singapore", + children: "GraphQL Day NYC", + href: "/day/2026/nyc", }, ]} /> From 6d14d5cfd5e805af0f2d3dd380f75fe201b5864d Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Tue, 28 Apr 2026 23:58:40 +0200 Subject: [PATCH 18/18] Add bottom padding to the Schedule header --- src/app/day/2026/singapore/schedule-section.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/day/2026/singapore/schedule-section.tsx b/src/app/day/2026/singapore/schedule-section.tsx index a08e88b9f3..3e5229c083 100644 --- a/src/app/day/2026/singapore/schedule-section.tsx +++ b/src/app/day/2026/singapore/schedule-section.tsx @@ -40,7 +40,7 @@ export function ScheduleSection() {

-
+

Schedule

All times in Singapore Time (SGT, UTC+8)