From 453bc9c4f38814c5e0e3ace705d8283f59cad684 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 3 Nov 2025 11:40:57 +0100 Subject: [PATCH 001/133] Switch to raw layout for Events and Meetups page --- src/pages/community/_meta.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/community/_meta.ts b/src/pages/community/_meta.ts index fe3b4d1f5b..9ee129efa6 100644 --- a/src/pages/community/_meta.ts +++ b/src/pages/community/_meta.ts @@ -1,3 +1,5 @@ +import theme from "tailwindcss/defaultTheme" + export default { resources: "Resources", "tools-and-libraries": { @@ -5,7 +7,11 @@ export default { layout: "raw", }, }, - events: "", + events: { + theme: { + layout: "raw", + }, + }, ambassadors: "Ambassador Program", contribute: "Contribute to GraphQL", foundation: "Foundation", From abab5908c3388cfa21a4814546419999648c8ae8 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 6 Nov 2025 23:03:07 +0100 Subject: [PATCH 002/133] wip --- src/app/(main)/community/events/page.tsx | 364 +++++++++++++++++++++++ src/app/(main)/layout.tsx | 3 + src/app/not-found.tsx | 5 +- src/pages/community/events.mdx | 346 --------------------- 4 files changed, 368 insertions(+), 350 deletions(-) create mode 100644 src/app/(main)/community/events/page.tsx delete mode 100644 src/pages/community/events.mdx diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx new file mode 100644 index 0000000000..eb7ba34087 --- /dev/null +++ b/src/app/(main)/community/events/page.tsx @@ -0,0 +1,364 @@ +// --- +// title: Events & Meetups +// --- + +// # Events & Meetups + +import { LocationIcon, ClockIcon } from "../../../../icons" +import { clsx } from "clsx" +import { useEffect } from "react" +import { useData } from "nextra/hooks" +import "leaflet/dist/leaflet.css" + +import { meetups } from "../../../../components/meetups" +import { events, EventCard } from "../../../../components/events" +import pinkCircle from "./pink-circle.svg" +import { Button } from "../../../conf/_design-system/button" +import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" + +const { pastEvents, upcomingEvents } = events.reduce( + (acc, event) => { + const now = new Date() + const date = new Date(event.date) + if (date < now) { + acc.pastEvents.push(event) + } else { + acc.upcomingEvents.push(event) + } + return acc + }, + { pastEvents: [], upcomingEvents: [] }, +) + +export function EventsScrollview({ children }) { + return ( +
+ {children} +
+ ) +} + +export function Events({ events }) { + if (events.length === 0) return null + + return ( + + {events.map(event => ( + + ))} + + ) +} + +// + +// ## Past Events + +// + +// ## Meetups + +// If you are interested in hosting a GraphQL meetup, The GraphQL Foundation is +// happy to promote your GraphQL event through the +// [official communication channels](/community/#official-channels). + +// Please contact us in the `#meetups-admin` channel on +// [the community Discord channel](/community/#official-channels). + +//
+// +//
+ +// export function Meetups() { +// useEffect(() => { +// // Load only on client +// import("leaflet").then(L => { +// // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) +// // and replace default marker image +// L.Icon.Default.mergeOptions({ +// iconRetinaUrl: pinkCircle.src, +// shadowUrl: "", +// }) +// const map = L.map("map").setView([45, -15], 2) +// L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) +// for (const { node } of meetups) { +// L.marker([node.latitude, node.longitude]) +// .addTo(map) +// .bindPopup( +// `${node.name}`, +// ) +// } +// }) +// }, []) +// return ( +// <> +//
+// +// {meetups.map(({ node }) => ( +// +// ))} +// +// +// ) +// } + +// + +// {/* +// # Join the GraphQL Community + +// The GraphQL community is growing fast, with conferences, workshops, and meetups happening around the world. + +// Join our community to stay up to date with the latest developments, to learn from your peers, and to share your own experiences. + +// Browse the list of community meetups below, or search through the GraphQL events to find an event near you. + +// export const CommunityIntro = () => { +// const { meetups } = useData() +// return ( +//

+// Join one of {meetups.length} GraphQL Meetups around the +// world! +//

+// ) +// } + +// export const UrlList = ({ urls }) => { +// return ( +//
+// +//
+// ) +// } + +// export const CommunityList = () => { +// const data = useData(); +// const meetups = data.meetups; +// const unsetContinents = meetups.map((meetup) => meetup.continent); +// const continents = [...new Set(unsetContinents)]; + +// return ( +//
+//
+//
+// {continents.map((continent, index) => { +// const urls = meetups.filter( +// (meetup) => meetup.continent === continent +// ); + +// return ( +//
+//

{continent}

+// +//
+// ); +// })} +//
+//
+//
+// ) +// } + +// export const EventsIntro = () => { +// const { events } = useData(); +// return

Join one of {events.length} recent or upcoming GraphQL events around the world!

+// } + +// export const EventsList = () => { +// const data = useData(); +// const events = data.events; + +// return ( +//
+//
+//
+// {events.map((event) => { +// return ( +//
+// {event.name} +//
+// +// {event.name} +// +//

+// Hosted by:{" "} +// +// {event.host} +// +//

+//
+//
+//

+// Location:{" "} +// {event.location} +//

+//
+//
+// ); +// })} +//
+//
+//
+// ) +// } + +// ## GraphQL Communities +// + +// + +// ## GraphQL Events +// + +// + +// ## Events +// */} +// {/* _None currently scheduled_ */} + +// {/* Event template, copy and paste what you need. Please note that the only three required fields are the name of the event, who is organizing and hosting it, and the link to the code of conduct. Events without this information can't be posted. + +// ### [Name of the event - REQUIRED] + +// * **Date(s):** [date] +// * **Location:** [city, state, country|Virtual|Hybrid] +// * **Registration:** [link to reg site, with cost] +// * **CFP:** [link to CFP site] +// * **Schedule:** [link to schedule site] +// * **Host:** [name of organization or company hosting the event - REQUIRED] +// * **Code of Conduct:** [link to code of conduct - REQUIRED] + +// */} + +// {/* + +// ### GraphQLConf 2023 + +// - **Date:** September 19-21, 2023 +// - **Location:** San Francisco Bay Area, CA +// - **Host:** GraphQL Foundation +// - [**Registration**](https://graphql.org/conf/#attend) +// - [**Schedule**](https://graphql.org/conf/schedule/) +// - [**Code of Conduct**](https://graphql.org/conf/faq/#codeofconduct) + +// ## Meetups + +// ### North America + +// - [GraphQL San Francisco](https://www.meetup.com/sf-graphql/) +// - [GraphQL APIs (San Francisco)](http://www.meetup.com/graphql/) +// - [GraphQL By the Bay (San Francisco)](https://www.meetup.com/graphql-by-the-bay/) +// - [GraphQL Seattle](https://www.meetup.com/seattlegraphql/) +// - [GraphQL Boston](https://www.meetup.com/graphql-boston/) +// - [GraphQL NYC](https://www.meetup.com/graphql-newyork/) +// - [GraphQL Austin](https://www.meetup.com/ATX-GraphQL/) +// - [GraphQL Minneapolis](https://www.meetup.com/GraphQL-MN/) +// - [GraphQL Denver](https://www.meetup.com/graphql-denver) +// - [GraphQL Chicago](https://www.meetup.com/graphql-chicago/) + +// ### South America + +// - [GraphQL São Paulo](https://www.meetup.com/graphql-sp/) +// - [GraphQL Buenos Aires](https://www.meetup.com/GraphQL-BA/) + +// ### Europe + +// - [GraphQL Amsterdam](https://www.meetup.com/Amsterdam-GraphQL-Meetup/) +// - [GraphQL Berlin](https://www.meetup.com/graphql-berlin/) +// - [GraphQL Barcelona](https://www.meetup.com/GraphQL-Barcelona/) +// - [GraphQL Budapest](https://www.meetup.com/Budapest-GraphQL/) +// - [GraphQL Copenhagen](https://www.meetup.com/Copenhagen-GraphQL-Meetup-Group/) +// - [GraphQL London](https://guild.host/graphql-london/events) +// - [GraphQL Milano](https://www.meetup.com/GraphQL-Milano/) +// - [GraphQL Munich](https://www.meetup.com/GraphQL-Munich/) +// - [GraphQL Paris](https://www.meetup.com/GraphQL-Paris/) +// - [GraphQL Paris 2](https://www.meetup.com/fr-FR/parisgraphql/) +// - [GraphQL Sarajevo](https://www.meetup.com/graphql-sarajevo/) +// - [GraphQL Stockholm](https://www.meetup.com/GraphQL-Stockholm/) +// - [GraphQL Zurich](https://www.meetup.com/graphql-zurich/) +// - [GraphQL Wroclaw](https://www.meetup.com/GraphQL-Wroclaw/) + +// ### Australia + +// - [GraphQL Melbourne](http://graphql.melbourne/) +// - [GraphQL Sydney](https://graphql.sydney/) + +// ### Asia + +// - [GraphQL Tel Aviv](https://www.meetup.com/GraphQL-TLV/) +// - [GraphQL Tokyo](https://www.meetup.com/GraphQL-Tokyo/) +// - [GraphQL Meetup (Bangalore)](https://www.meetup.com/graphql-bangalore/) +// - [GraphQL Meetup (Bangkok)](https://www.meetup.com/GraphQL-Bangkok/) +// - [GraphQL Meetup (Singapore)](https://www.meetup.com/GraphQL-SG/) +// - [GraphQL Meetup (Hong Kong)](https://www.meetup.com/GraphQLHongKong/) +// - [GraphQL Shenzhen](https://www.meetup.com/graphqlshenzhen/) +// - [GraphQL Korea](https://www.facebook.com/groups/graphql.kr) +// - [GraphQL Seoul](https://www.meetup.com/graphql-seoul/) + +// ### Africa + +// - [GraphQL Nairobi](https://www.meetup.com/Nairobi-GraphQL-Meetup/) + +// */} + +export default function EventsPage() { + return ( +
+

Events and Meetups

+ +
+ ) +} diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index fefeb27741..269636a62d 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -7,6 +7,9 @@ import { Navbar } from "../../components/navbar/navbar" import { topLevelNavbarItems } from "../../components/navbar/top-level-items" import { MenuProvider } from "./menu-provider" +import "@/globals.css" +import "@/app/colors.css" + export default function MainLayout({ children, }: { diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index e6777b15d4..e848d35314 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -9,9 +9,6 @@ import stripesMask from "@/components/404-page/image.webp" import { Button } from "./conf/_design-system/button" import MainLayout from "./(main)/layout" -import "@/globals.css" -import "@/app/colors.css" - export default function NotFoundPage() { const pathname = usePathname() const mounted = useMounted() @@ -35,7 +32,7 @@ export default function NotFoundPage() {
-
+

Page not found

diff --git a/src/pages/community/events.mdx b/src/pages/community/events.mdx deleted file mode 100644 index 6cb4de8908..0000000000 --- a/src/pages/community/events.mdx +++ /dev/null @@ -1,346 +0,0 @@ ---- -title: Events & Meetups ---- - -# Events & Meetups - -import { LocationIcon, ClockIcon } from "../../icons" -import { clsx } from "clsx" -import { useEffect } from "react" -import { useData } from "nextra/hooks" -import "leaflet/dist/leaflet.css" - -import { meetups } from "../../components/meetups" -import { events, EventCard } from "../../components/events" -import pinkCircle from "./pink-circle.svg" -import { Button } from '../../app/conf/_design-system/button' - - -export const { pastEvents, upcomingEvents } = events.reduce( - (acc, event) => { - const now = new Date() - const date = new Date(event.date) - if (date < now) { - acc.pastEvents.push(event) - } else { - acc.upcomingEvents.push(event) - } - return acc - }, - { pastEvents: [], upcomingEvents: [] }, -) - -export function EventsScrollview({ children }) { - return ( -
- {children} -
- ) -} - -export function Events({ events }) { - if (events.length === 0) return null; - - return ( - - {events.map(event => ( - - ))} - - ) -} - -## Upcoming Events - - - -## Past Events - - - -## Meetups - -If you are interested in hosting a GraphQL meetup, The GraphQL Foundation is -happy to promote your GraphQL event through the -[official communication channels](/community/#official-channels). - -Please contact us in the `#meetups-admin` channel on -[the community Discord channel](/community/#official-channels). - -
- -
- -export function Meetups() { - useEffect(() => { - // Load only on client - import("leaflet").then(L => { - // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) - // and replace default marker image - L.Icon.Default.mergeOptions({ - iconRetinaUrl: pinkCircle.src, - shadowUrl: "", - }) - const map = L.map("map").setView([45, -15], 2) - L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) - for (const { node } of meetups) { - L.marker([node.latitude, node.longitude]) - .addTo(map) - .bindPopup( - `${node.name}`, - ) - } - }) - }, []) - return ( - <> -
- - {meetups.map(({ node }) => ( - - ))} - - - ) -} - - - -{/* -# Join the GraphQL Community - -The GraphQL community is growing fast, with conferences, workshops, and meetups happening around the world. - -Join our community to stay up to date with the latest developments, to learn from your peers, and to share your own experiences. - -Browse the list of community meetups below, or search through the GraphQL events to find an event near you. - -export const CommunityIntro = () => { - const { meetups } = useData() - return ( -

- Join one of {meetups.length} GraphQL Meetups around the - world! -

- ) -} - -export const UrlList = ({ urls }) => { - return ( -
- -
- ) -} - -export const CommunityList = () => { - const data = useData(); - const meetups = data.meetups; - const unsetContinents = meetups.map((meetup) => meetup.continent); - const continents = [...new Set(unsetContinents)]; - -return ( -
-
-
-{continents.map((continent, index) => { -const urls = meetups.filter( -(meetup) => meetup.continent === continent -); - - return ( -
-

{continent}

- -
- ); - })} -
-
-
-) -} - -export const EventsIntro = () => { - const { events } = useData(); - return

Join one of {events.length} recent or upcoming GraphQL events around the world!

-} - -export const EventsList = () => { -const data = useData(); -const events = data.events; - - return ( -
-
-
- {events.map((event) => { - return ( -
- {event.name} -
- - {event.name} - -

- Hosted by:{" "} - - {event.host} - -

-
-
-

- Location:{" "} - {event.location} -

-
-
- ); - })} -
-
-
- ) -} - -## GraphQL Communities - - - - -## GraphQL Events - - - - -## Events -*/} -{/* _None currently scheduled_ */} - -{/* Event template, copy and paste what you need. Please note that the only three required fields are the name of the event, who is organizing and hosting it, and the link to the code of conduct. Events without this information can't be posted. - -### [Name of the event - REQUIRED] - -* **Date(s):** [date] -* **Location:** [city, state, country|Virtual|Hybrid] -* **Registration:** [link to reg site, with cost] -* **CFP:** [link to CFP site] -* **Schedule:** [link to schedule site] -* **Host:** [name of organization or company hosting the event - REQUIRED] -* **Code of Conduct:** [link to code of conduct - REQUIRED] - -*/} - -{/* - -### GraphQLConf 2023 - -- **Date:** September 19-21, 2023 -- **Location:** San Francisco Bay Area, CA -- **Host:** GraphQL Foundation -- [**Registration**](https://graphql.org/conf/#attend) -- [**Schedule**](https://graphql.org/conf/schedule/) -- [**Code of Conduct**](https://graphql.org/conf/faq/#codeofconduct) - -## Meetups - -### North America - -- [GraphQL San Francisco](https://www.meetup.com/sf-graphql/) -- [GraphQL APIs (San Francisco)](http://www.meetup.com/graphql/) -- [GraphQL By the Bay (San Francisco)](https://www.meetup.com/graphql-by-the-bay/) -- [GraphQL Seattle](https://www.meetup.com/seattlegraphql/) -- [GraphQL Boston](https://www.meetup.com/graphql-boston/) -- [GraphQL NYC](https://www.meetup.com/graphql-newyork/) -- [GraphQL Austin](https://www.meetup.com/ATX-GraphQL/) -- [GraphQL Minneapolis](https://www.meetup.com/GraphQL-MN/) -- [GraphQL Denver](https://www.meetup.com/graphql-denver) -- [GraphQL Chicago](https://www.meetup.com/graphql-chicago/) - -### South America - -- [GraphQL São Paulo](https://www.meetup.com/graphql-sp/) -- [GraphQL Buenos Aires](https://www.meetup.com/GraphQL-BA/) - -### Europe - -- [GraphQL Amsterdam](https://www.meetup.com/Amsterdam-GraphQL-Meetup/) -- [GraphQL Berlin](https://www.meetup.com/graphql-berlin/) -- [GraphQL Barcelona](https://www.meetup.com/GraphQL-Barcelona/) -- [GraphQL Budapest](https://www.meetup.com/Budapest-GraphQL/) -- [GraphQL Copenhagen](https://www.meetup.com/Copenhagen-GraphQL-Meetup-Group/) -- [GraphQL London](https://guild.host/graphql-london/events) -- [GraphQL Milano](https://www.meetup.com/GraphQL-Milano/) -- [GraphQL Munich](https://www.meetup.com/GraphQL-Munich/) -- [GraphQL Paris](https://www.meetup.com/GraphQL-Paris/) -- [GraphQL Paris 2](https://www.meetup.com/fr-FR/parisgraphql/) -- [GraphQL Sarajevo](https://www.meetup.com/graphql-sarajevo/) -- [GraphQL Stockholm](https://www.meetup.com/GraphQL-Stockholm/) -- [GraphQL Zurich](https://www.meetup.com/graphql-zurich/) -- [GraphQL Wroclaw](https://www.meetup.com/GraphQL-Wroclaw/) - -### Australia - -- [GraphQL Melbourne](http://graphql.melbourne/) -- [GraphQL Sydney](https://graphql.sydney/) - -### Asia - -- [GraphQL Tel Aviv](https://www.meetup.com/GraphQL-TLV/) -- [GraphQL Tokyo](https://www.meetup.com/GraphQL-Tokyo/) -- [GraphQL Meetup (Bangalore)](https://www.meetup.com/graphql-bangalore/) -- [GraphQL Meetup (Bangkok)](https://www.meetup.com/GraphQL-Bangkok/) -- [GraphQL Meetup (Singapore)](https://www.meetup.com/GraphQL-SG/) -- [GraphQL Meetup (Hong Kong)](https://www.meetup.com/GraphQLHongKong/) -- [GraphQL Shenzhen](https://www.meetup.com/graphqlshenzhen/) -- [GraphQL Korea](https://www.facebook.com/groups/graphql.kr) -- [GraphQL Seoul](https://www.meetup.com/graphql-seoul/) - -### Africa - -- [GraphQL Nairobi](https://www.meetup.com/Nairobi-GraphQL-Meetup/) - -*/} From 27196acbe495fe657bea01d01867717a8ee85324 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 6 Nov 2025 23:17:23 +0100 Subject: [PATCH 003/133] Fix the navbar in 404 page --- src/components/navbar/top-level-items.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/navbar/top-level-items.tsx b/src/components/navbar/top-level-items.tsx index ff4b9d499d..8fd1a8ee68 100644 --- a/src/components/navbar/top-level-items.tsx +++ b/src/components/navbar/top-level-items.tsx @@ -1,4 +1,3 @@ -// src/components/navbar/normalize-meta-to-items.ts import { normalizePages } from "nextra/normalize-pages" import type { Folder, PageMapItem } from "nextra" import meta from "../../pages/_meta" @@ -6,21 +5,23 @@ import meta from "../../pages/_meta" /** * Convert _meta.tsx format to PageMapItem[] format that normalizePages expects */ -export function normalizeMetaToItems(meta: Record, route = "/") { +export function normalizeMetaToItems(meta: Record, parent = "/") { const pageMapItems: PageMapItem[] = Object.entries(meta).map( ([name, config]) => { + const route = parent === "/" ? `/${name}` : `${parent}/${name}` + if (typeof config === "string") { return { kind: "MdxPage", name, - route: `/${name}`, + route, frontMatter: { title: config }, } } const item: PageMapItem = { name, - route: config.route || `/${name}`, + route: config.route || route, } if (config.title) { @@ -40,7 +41,7 @@ export function normalizeMetaToItems(meta: Record, route = "/") { const result = normalizePages({ list: [{ data: meta }, ...pageMapItems], - route, + route: parent, }) return result From 5dff18000f083368aaa28b7d25a6cc8ddac758aa Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 6 Nov 2025 23:26:15 +0100 Subject: [PATCH 004/133] Move events page to `app` directory --- src/app/(main)/community/events/page.tsx | 400 +++++------------------ src/components/events/index.ts | 2 +- src/pages/_meta.tsx | 6 +- src/pages/community/_meta.ts | 7 +- 4 files changed, 97 insertions(+), 318 deletions(-) diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index eb7ba34087..edfedfb234 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -4,17 +4,13 @@ // # Events & Meetups -import { LocationIcon, ClockIcon } from "../../../../icons" -import { clsx } from "clsx" -import { useEffect } from "react" -import { useData } from "nextra/hooks" -import "leaflet/dist/leaflet.css" +import type { ReactNode } from "react" +import type { Event } from "../../../../components/events" -import { meetups } from "../../../../components/meetups" import { events, EventCard } from "../../../../components/events" -import pinkCircle from "./pink-circle.svg" -import { Button } from "../../../conf/_design-system/button" import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" +import { meetups } from "../../../../components/meetups" +import Link from "next/link" const { pastEvents, upcomingEvents } = events.reduce( (acc, event) => { @@ -27,10 +23,13 @@ const { pastEvents, upcomingEvents } = events.reduce( } return acc }, - { pastEvents: [], upcomingEvents: [] }, + { pastEvents: [], upcomingEvents: [] } as { + pastEvents: Event[] + upcomingEvents: Event[] + }, ) -export function EventsScrollview({ children }) { +export function EventsScrollview({ children }: { children: ReactNode }) { return (
{children} @@ -38,7 +37,7 @@ export function EventsScrollview({ children }) { ) } -export function Events({ events }) { +export function Events({ events }: { events: Event[] }) { if (events.length === 0) return null return ( @@ -57,308 +56,87 @@ export function Events({ events }) { ) } -// - -// ## Past Events - -// - -// ## Meetups - -// If you are interested in hosting a GraphQL meetup, The GraphQL Foundation is -// happy to promote your GraphQL event through the -// [official communication channels](/community/#official-channels). - -// Please contact us in the `#meetups-admin` channel on -// [the community Discord channel](/community/#official-channels). - -//
-// -//
- -// export function Meetups() { -// useEffect(() => { -// // Load only on client -// import("leaflet").then(L => { -// // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) -// // and replace default marker image -// L.Icon.Default.mergeOptions({ -// iconRetinaUrl: pinkCircle.src, -// shadowUrl: "", -// }) -// const map = L.map("map").setView([45, -15], 2) -// L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) -// for (const { node } of meetups) { -// L.marker([node.latitude, node.longitude]) -// .addTo(map) -// .bindPopup( -// `${node.name}`, -// ) -// } -// }) -// }, []) -// return ( -// <> -//
-// -// {meetups.map(({ node }) => ( -// -// ))} -// -// -// ) -// } - -// - -// {/* -// # Join the GraphQL Community - -// The GraphQL community is growing fast, with conferences, workshops, and meetups happening around the world. - -// Join our community to stay up to date with the latest developments, to learn from your peers, and to share your own experiences. - -// Browse the list of community meetups below, or search through the GraphQL events to find an event near you. - -// export const CommunityIntro = () => { -// const { meetups } = useData() -// return ( -//

-// Join one of {meetups.length} GraphQL Meetups around the -// world! -//

-// ) -// } - -// export const UrlList = ({ urls }) => { -// return ( -//
-// -//
-// ) -// } - -// export const CommunityList = () => { -// const data = useData(); -// const meetups = data.meetups; -// const unsetContinents = meetups.map((meetup) => meetup.continent); -// const continents = [...new Set(unsetContinents)]; - -// return ( -//
-//
-//
-// {continents.map((continent, index) => { -// const urls = meetups.filter( -// (meetup) => meetup.continent === continent -// ); - -// return ( -//
-//

{continent}

-// -//
-// ); -// })} -//
-//
-//
-// ) -// } - -// export const EventsIntro = () => { -// const { events } = useData(); -// return

Join one of {events.length} recent or upcoming GraphQL events around the world!

-// } - -// export const EventsList = () => { -// const data = useData(); -// const events = data.events; - -// return ( -//
-//
-//
-// {events.map((event) => { -// return ( -//
-// {event.name} -//
-// -// {event.name} -// -//

-// Hosted by:{" "} -// -// {event.host} -// -//

-//
-//
-//

-// Location:{" "} -// {event.location} -//

-//
-//
-// ); -// })} -//
-//
-//
-// ) -// } - -// ## GraphQL Communities -// - -// - -// ## GraphQL Events -// - -// - -// ## Events -// */} -// {/* _None currently scheduled_ */} - -// {/* Event template, copy and paste what you need. Please note that the only three required fields are the name of the event, who is organizing and hosting it, and the link to the code of conduct. Events without this information can't be posted. - -// ### [Name of the event - REQUIRED] - -// * **Date(s):** [date] -// * **Location:** [city, state, country|Virtual|Hybrid] -// * **Registration:** [link to reg site, with cost] -// * **CFP:** [link to CFP site] -// * **Schedule:** [link to schedule site] -// * **Host:** [name of organization or company hosting the event - REQUIRED] -// * **Code of Conduct:** [link to code of conduct - REQUIRED] - -// */} - -// {/* - -// ### GraphQLConf 2023 - -// - **Date:** September 19-21, 2023 -// - **Location:** San Francisco Bay Area, CA -// - **Host:** GraphQL Foundation -// - [**Registration**](https://graphql.org/conf/#attend) -// - [**Schedule**](https://graphql.org/conf/schedule/) -// - [**Code of Conduct**](https://graphql.org/conf/faq/#codeofconduct) - -// ## Meetups - -// ### North America - -// - [GraphQL San Francisco](https://www.meetup.com/sf-graphql/) -// - [GraphQL APIs (San Francisco)](http://www.meetup.com/graphql/) -// - [GraphQL By the Bay (San Francisco)](https://www.meetup.com/graphql-by-the-bay/) -// - [GraphQL Seattle](https://www.meetup.com/seattlegraphql/) -// - [GraphQL Boston](https://www.meetup.com/graphql-boston/) -// - [GraphQL NYC](https://www.meetup.com/graphql-newyork/) -// - [GraphQL Austin](https://www.meetup.com/ATX-GraphQL/) -// - [GraphQL Minneapolis](https://www.meetup.com/GraphQL-MN/) -// - [GraphQL Denver](https://www.meetup.com/graphql-denver) -// - [GraphQL Chicago](https://www.meetup.com/graphql-chicago/) - -// ### South America - -// - [GraphQL São Paulo](https://www.meetup.com/graphql-sp/) -// - [GraphQL Buenos Aires](https://www.meetup.com/GraphQL-BA/) - -// ### Europe - -// - [GraphQL Amsterdam](https://www.meetup.com/Amsterdam-GraphQL-Meetup/) -// - [GraphQL Berlin](https://www.meetup.com/graphql-berlin/) -// - [GraphQL Barcelona](https://www.meetup.com/GraphQL-Barcelona/) -// - [GraphQL Budapest](https://www.meetup.com/Budapest-GraphQL/) -// - [GraphQL Copenhagen](https://www.meetup.com/Copenhagen-GraphQL-Meetup-Group/) -// - [GraphQL London](https://guild.host/graphql-london/events) -// - [GraphQL Milano](https://www.meetup.com/GraphQL-Milano/) -// - [GraphQL Munich](https://www.meetup.com/GraphQL-Munich/) -// - [GraphQL Paris](https://www.meetup.com/GraphQL-Paris/) -// - [GraphQL Paris 2](https://www.meetup.com/fr-FR/parisgraphql/) -// - [GraphQL Sarajevo](https://www.meetup.com/graphql-sarajevo/) -// - [GraphQL Stockholm](https://www.meetup.com/GraphQL-Stockholm/) -// - [GraphQL Zurich](https://www.meetup.com/graphql-zurich/) -// - [GraphQL Wroclaw](https://www.meetup.com/GraphQL-Wroclaw/) - -// ### Australia - -// - [GraphQL Melbourne](http://graphql.melbourne/) -// - [GraphQL Sydney](https://graphql.sydney/) - -// ### Asia - -// - [GraphQL Tel Aviv](https://www.meetup.com/GraphQL-TLV/) -// - [GraphQL Tokyo](https://www.meetup.com/GraphQL-Tokyo/) -// - [GraphQL Meetup (Bangalore)](https://www.meetup.com/graphql-bangalore/) -// - [GraphQL Meetup (Bangkok)](https://www.meetup.com/GraphQL-Bangkok/) -// - [GraphQL Meetup (Singapore)](https://www.meetup.com/GraphQL-SG/) -// - [GraphQL Meetup (Hong Kong)](https://www.meetup.com/GraphQLHongKong/) -// - [GraphQL Shenzhen](https://www.meetup.com/graphqlshenzhen/) -// - [GraphQL Korea](https://www.facebook.com/groups/graphql.kr) -// - [GraphQL Seoul](https://www.meetup.com/graphql-seoul/) - -// ### Africa - -// - [GraphQL Nairobi](https://www.meetup.com/Nairobi-GraphQL-Meetup/) - -// */} - export default function EventsPage() { return ( -
-

Events and Meetups

- +
+

Events & Meetups

+ +
+ +
+ + {upcomingEvents.length > 0 && ( +
+

Upcoming Events

+ +
+ )} + +
+

Past Events

+ +
+ +
+

Meetups

+

+ If you are interested in hosting a GraphQL meetup, The GraphQL + Foundation is happy to promote your GraphQL event through the{" "} + + official communication channels + + . +

+ +

+ Please contact us in the #meetups-admin channel on{" "} + + the community Discord channel + + . +

+ +
+ + Start a GraphQL Local! + +
+ + + {meetups.map(({ node }) => ( + + ))} + +
) } diff --git a/src/components/events/index.ts b/src/components/events/index.ts index 8befb6af03..13805a111d 100644 --- a/src/components/events/index.ts +++ b/src/components/events/index.ts @@ -1,6 +1,6 @@ export * from "./event-card" -interface Event { +export interface Event { name: string slug: string location: string diff --git a/src/pages/_meta.tsx b/src/pages/_meta.tsx index b37d873d66..6c29f83ce9 100644 --- a/src/pages/_meta.tsx +++ b/src/pages/_meta.tsx @@ -21,7 +21,11 @@ export default { title: "Resources", href: "/community/resources/official-channels", }, - events: { title: "Events & Meetups" }, + events: { + title: "Events & Meetups", + type: "page", + href: "/community/events", + }, ambassadors: { title: "Ambassador Program" }, contribute: { title: "Contribute to GraphQL", diff --git a/src/pages/community/_meta.ts b/src/pages/community/_meta.ts index 9ee129efa6..1746267b98 100644 --- a/src/pages/community/_meta.ts +++ b/src/pages/community/_meta.ts @@ -1,5 +1,3 @@ -import theme from "tailwindcss/defaultTheme" - export default { resources: "Resources", "tools-and-libraries": { @@ -8,9 +6,8 @@ export default { }, }, events: { - theme: { - layout: "raw", - }, + type: "page", + href: "/community/events", }, ambassadors: "Ambassador Program", contribute: "Contribute to GraphQL", From aed8b2be23848d22bc556a956d959be64fdb3129 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 00:04:26 +0100 Subject: [PATCH 005/133] Draft Benefits section --- src/app/(main)/community/events/page.tsx | 144 +++++++++++++++++- .../_design-system/pixelarticons/comment.svg | 3 + .../conf/_design-system/pixelarticons/eye.svg | 3 + .../_design-system/pixelarticons/sliders.svg | 3 + .../_design-system/pixelarticons/users.svg | 3 + 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/app/conf/_design-system/pixelarticons/comment.svg create mode 100644 src/app/conf/_design-system/pixelarticons/eye.svg create mode 100644 src/app/conf/_design-system/pixelarticons/sliders.svg create mode 100644 src/app/conf/_design-system/pixelarticons/users.svg diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index edfedfb234..f4f86d6606 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -4,13 +4,22 @@ // # Events & Meetups -import type { ReactNode } from "react" +"use client" + +import type { ComponentType, ReactNode, SVGProps } from "react" import type { Event } from "../../../../components/events" import { events, EventCard } from "../../../../components/events" import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" import { meetups } from "../../../../components/meetups" import Link from "next/link" +import UsersIcon from "@/app/conf/_design-system/pixelarticons/users.svg?svgr" +import CommentIcon from "@/app/conf/_design-system/pixelarticons/comment.svg?svgr" +import SlidersIcon from "@/app/conf/_design-system/pixelarticons/sliders.svg?svgr" +import EyeIcon from "@/app/conf/_design-system/pixelarticons/eye.svg?svgr" +import { useEffect, useRef } from "react" +import "leaflet/dist/leaflet.css" +import pinkCircle from "../../../../pages/community/pink-circle.svg" const { pastEvents, upcomingEvents } = events.reduce( (acc, event) => { @@ -56,6 +65,67 @@ export function Events({ events }: { events: Event[] }) { ) } +function BenefitCard({ + title, + description, + icon: Icon, +}: { + title: string + description: string + icon: ComponentType> +}) { + return ( +
+ +
+

{title}

+

{description}

+
+
+ ) +} + +function MeetupsMap() { + const mapRef = useRef(null) + const mapInstanceRef = useRef(null) + + useEffect(() => { + if (!mapRef.current || mapInstanceRef.current) return + + // Load only on client + import("leaflet").then(L => { + // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) + // and replace default marker image + L.Icon.Default.mergeOptions({ + iconRetinaUrl: pinkCircle.src, + shadowUrl: "", + }) + + const map = L.map(mapRef.current!).setView([45, -15], 2) + mapInstanceRef.current = map + + L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) + + for (const { node } of meetups) { + L.marker([node.latitude, node.longitude]) + .addTo(map) + .bindPopup( + `${node.name}`, + ) + } + }) + + return () => { + if (mapInstanceRef.current) { + mapInstanceRef.current.remove() + mapInstanceRef.current = null + } + } + }, []) + + return
+} + export default function EventsPage() { return (
@@ -84,6 +154,41 @@ export default function EventsPage() { />
+
+
+

+ Benefits of getting involved +

+

+ Contributing to GraphQL means more than writing code — it’s a chance + to collaborate, share ideas, and shape the future of the ecosystem. +

+
+ +
+ + + + +
+
+ {upcomingEvents.length > 0 && (

Upcoming Events

@@ -124,6 +229,8 @@ export default function EventsPage() {
+ + {meetups.map(({ node }) => ( + +
+
+

+ Benefits of getting involved +

+

+ Contributing to GraphQL means more than writing code — it's a chance + to collaborate, share ideas, and shape the future of the ecosystem. +

+
+ +
+ + + + +
+
) } diff --git a/src/app/conf/_design-system/pixelarticons/comment.svg b/src/app/conf/_design-system/pixelarticons/comment.svg new file mode 100644 index 0000000000..c02110cea5 --- /dev/null +++ b/src/app/conf/_design-system/pixelarticons/comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/conf/_design-system/pixelarticons/eye.svg b/src/app/conf/_design-system/pixelarticons/eye.svg new file mode 100644 index 0000000000..d4aaffb071 --- /dev/null +++ b/src/app/conf/_design-system/pixelarticons/eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/conf/_design-system/pixelarticons/sliders.svg b/src/app/conf/_design-system/pixelarticons/sliders.svg new file mode 100644 index 0000000000..facfa787ba --- /dev/null +++ b/src/app/conf/_design-system/pixelarticons/sliders.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/conf/_design-system/pixelarticons/users.svg b/src/app/conf/_design-system/pixelarticons/users.svg new file mode 100644 index 0000000000..750fbfbf19 --- /dev/null +++ b/src/app/conf/_design-system/pixelarticons/users.svg @@ -0,0 +1,3 @@ + + + From e0305d5565a322ada670a41f5e62c293f4840718 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 13:18:26 +0100 Subject: [PATCH 006/133] Move sections to separate files --- package.json | 1 + pnpm-lock.yaml | 10 + .../(main)/community/events/benefit-card.tsx | 21 ++ .../(main)/community}/events/event-card.tsx | 2 +- .../community/events/events-scrollview.tsx | 9 + .../(main)/community/events/events.ts} | 0 src/app/(main)/community/events/mailbox.svg | 19 ++ src/app/(main)/community/events/meetups.tsx | 48 ++++ src/app/(main)/community/events/page.tsx | 251 +++++++----------- .../(main)/community/events}/pink-circle.svg | 0 10 files changed, 198 insertions(+), 163 deletions(-) create mode 100644 src/app/(main)/community/events/benefit-card.tsx rename src/{components => app/(main)/community}/events/event-card.tsx (98%) create mode 100644 src/app/(main)/community/events/events-scrollview.tsx rename src/{components/events/index.ts => app/(main)/community/events/events.ts} (100%) create mode 100644 src/app/(main)/community/events/mailbox.svg create mode 100644 src/app/(main)/community/events/meetups.tsx rename src/{pages/community => app/(main)/community/events}/pink-circle.svg (100%) diff --git a/package.json b/package.json index 87ca77c521..51054343fa 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/nesting": "0.0.0-insiders.565cd3e", "@tailwindcss/typography": "^0.5.15", + "@types/leaflet": "^1.9.21", "autoprefixer": "^10.4.20", "calendar-link": "^2.10.0", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d73c7bb42..39e5451429 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,6 +73,9 @@ importers: '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.19(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1)) + '@types/leaflet': + specifier: ^1.9.21 + version: 1.9.21 autoprefixer: specifier: ^10.4.20 version: 10.4.21(postcss@8.5.6) @@ -2119,6 +2122,9 @@ packages: '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/leaflet@1.9.21': + resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} + '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} @@ -8059,6 +8065,10 @@ snapshots: '@types/katex@0.16.7': {} + '@types/leaflet@1.9.21': + dependencies: + '@types/geojson': 7946.0.16 + '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.17.20 diff --git a/src/app/(main)/community/events/benefit-card.tsx b/src/app/(main)/community/events/benefit-card.tsx new file mode 100644 index 0000000000..39c987199a --- /dev/null +++ b/src/app/(main)/community/events/benefit-card.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from "react" + +export function BenefitCard({ + title, + description, + icon, +}: { + title: string + description: string + icon: ReactNode +}) { + return ( +
+ {icon} +
+

{title}

+

{description}

+
+
+ ) +} diff --git a/src/components/events/event-card.tsx b/src/app/(main)/community/events/event-card.tsx similarity index 98% rename from src/components/events/event-card.tsx rename to src/app/(main)/community/events/event-card.tsx index 944f746ba1..110daaaf54 100644 --- a/src/components/events/event-card.tsx +++ b/src/app/(main)/community/events/event-card.tsx @@ -3,7 +3,7 @@ import { clsx } from "clsx" import { CalendarIcon } from "@/app/conf/_design-system/pixelarticons/calendar-icon" import { PinIcon } from "@/app/conf/_design-system/pixelarticons/pin-icon" -import { Tag } from "../../app/conf/_design-system/tag" +import { Tag } from "@/app/conf/_design-system/tag" const dateFormatter = new Intl.DateTimeFormat("en", { day: "numeric", diff --git a/src/app/(main)/community/events/events-scrollview.tsx b/src/app/(main)/community/events/events-scrollview.tsx new file mode 100644 index 0000000000..d6897dbc17 --- /dev/null +++ b/src/app/(main)/community/events/events-scrollview.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from "react"; + +export function EventsScrollview({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ) +} diff --git a/src/components/events/index.ts b/src/app/(main)/community/events/events.ts similarity index 100% rename from src/components/events/index.ts rename to src/app/(main)/community/events/events.ts diff --git a/src/app/(main)/community/events/mailbox.svg b/src/app/(main)/community/events/mailbox.svg new file mode 100644 index 0000000000..aef5616825 --- /dev/null +++ b/src/app/(main)/community/events/mailbox.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups.tsx new file mode 100644 index 0000000000..b994ed4a33 --- /dev/null +++ b/src/app/(main)/community/events/meetups.tsx @@ -0,0 +1,48 @@ +import { useEffect } from "react" + +import "leaflet/dist/leaflet.css" +import { EventsScrollview } from "./events-scrollview" +import { meetups } from "../../../../components/meetups" +import { EventCard } from "./event-card" + +import pinkCircle from "./pink-cirle.svg" + +export function Meetups() { + useEffect(() => { + // Load only on client + import("leaflet").then(L => { + // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) + // and replace default marker image + L.Icon.Default.mergeOptions({ + iconRetinaUrl: pinkCircle.src, + shadowUrl: "", + }) + const map = L.map("map").setView([45, -15], 2) + L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) + for (const { node } of meetups) { + L.marker([node.latitude, node.longitude]) + .addTo(map) + .bindPopup( + `${node.name}`, + ) + } + }) + }, []) + return ( + <> +
+ + {meetups.map(({ node }) => ( + + ))} + + + ) +} diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index f4f86d6606..7e4a69b246 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -1,25 +1,20 @@ -// --- -// title: Events & Meetups -// --- - -// # Events & Meetups - "use client" -import type { ComponentType, ReactNode, SVGProps } from "react" -import type { Event } from "../../../../components/events" +import Link from "next/link" +import type { Event } from "./events" -import { events, EventCard } from "../../../../components/events" +import { events, EventCard } from "./events" import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" -import { meetups } from "../../../../components/meetups" -import Link from "next/link" + import UsersIcon from "@/app/conf/_design-system/pixelarticons/users.svg?svgr" import CommentIcon from "@/app/conf/_design-system/pixelarticons/comment.svg?svgr" import SlidersIcon from "@/app/conf/_design-system/pixelarticons/sliders.svg?svgr" import EyeIcon from "@/app/conf/_design-system/pixelarticons/eye.svg?svgr" -import { useEffect, useRef } from "react" -import "leaflet/dist/leaflet.css" -import pinkCircle from "../../../../pages/community/pink-circle.svg" + +import Mailbox from "./mailbox.svg?svgr" +import { Meetups } from "./meetups" +import { BenefitCard } from "./benefit-card" +import { EventsScrollview } from "./events-scrollview" const { pastEvents, upcomingEvents } = events.reduce( (acc, event) => { @@ -38,14 +33,6 @@ const { pastEvents, upcomingEvents } = events.reduce( }, ) -export function EventsScrollview({ children }: { children: ReactNode }) { - return ( -
- {children} -
- ) -} - export function Events({ events }: { events: Event[] }) { if (events.length === 0) return null @@ -65,67 +52,6 @@ export function Events({ events }: { events: Event[] }) { ) } -function BenefitCard({ - title, - description, - icon: Icon, -}: { - title: string - description: string - icon: ComponentType> -}) { - return ( -
- -
-

{title}

-

{description}

-
-
- ) -} - -function MeetupsMap() { - const mapRef = useRef(null) - const mapInstanceRef = useRef(null) - - useEffect(() => { - if (!mapRef.current || mapInstanceRef.current) return - - // Load only on client - import("leaflet").then(L => { - // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) - // and replace default marker image - L.Icon.Default.mergeOptions({ - iconRetinaUrl: pinkCircle.src, - shadowUrl: "", - }) - - const map = L.map(mapRef.current!).setView([45, -15], 2) - mapInstanceRef.current = map - - L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) - - for (const { node } of meetups) { - L.marker([node.latitude, node.longitude]) - .addTo(map) - .bindPopup( - `${node.name}`, - ) - } - }) - - return () => { - if (mapInstanceRef.current) { - mapInstanceRef.current.remove() - mapInstanceRef.current = null - } - } - }, []) - - return
-} - export default function EventsPage() { return (
@@ -155,37 +81,45 @@ export default function EventsPage() {
-
-

- Benefits of getting involved -

-

- Contributing to GraphQL means more than writing code — it’s a chance - to collaborate, share ideas, and shape the future of the ecosystem. -

-
- -
- - - - +
+
+

+ Submit your meetup +

+
+

+ Planning to host a GraphQL meetup? The GraphQL Foundation can + help spread the word through official channels. +

+

+ To submit your event, join our{" "} + + Discord + {" "} + and share details in the #meetups-admin channel. +

+
+
+ + Go to Discord + +
+
+
+
+ +
+
@@ -229,56 +163,49 @@ export default function EventsPage() {
- - - - {meetups.map(({ node }) => ( - - ))} - + -
-
-

- Benefits of getting involved -

-

- Contributing to GraphQL means more than writing code — it's a chance - to collaborate, share ideas, and shape the future of the ecosystem. -

-
- -
- - - - -
-
+
) } + +function BenefitsSection() { + return ( +
+
+

+ Benefits of getting involved +

+

+ Contributing to GraphQL means more than writing code — it’s a chance + to collaborate, share ideas, and shape the future of the ecosystem. +

+
+ +
+ } + title="Valuable networking opportunities" + description="Engage in conversations and hands-on projects to deepen your understanding of GraphQL." + /> + } + title="Collaborate with others" + description="Connect with contributors and teams building GraphQL tools and platforms." + /> + } + title="Help guide the spec" + description="Share ideas, give feedback, or participate in working groups to influence the future of GraphQL." + /> + } + title="Connect in real life" + description="Put a face to the handle — meet contributors in person at events and meetups. Build lasting connections beyond the screen." + /> +
+
+ ) +} diff --git a/src/pages/community/pink-circle.svg b/src/app/(main)/community/events/pink-circle.svg similarity index 100% rename from src/pages/community/pink-circle.svg rename to src/app/(main)/community/events/pink-circle.svg From 5ed798bae6b502674c0fbf93ff4bc568a5b93f98 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 15:20:29 +0100 Subject: [PATCH 007/133] Update Discord channel reference to #locals --- src/pages/community/foundation/contact.mdx | 2 +- src/pages/community/resources/more-resources.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/community/foundation/contact.mdx b/src/pages/community/foundation/contact.mdx index 1c89a6c4a7..968db9fac4 100644 --- a/src/pages/community/foundation/contact.mdx +++ b/src/pages/community/foundation/contact.mdx @@ -38,7 +38,7 @@ For media inquiries, please reach out to [pr@graphql.org](mailto:pr@graphql.org) If you are interested in hosting a GraphQL meetup, The GraphQL Foundation is happy to promote your GraphQL event through the [official communication channels](/community/#official-channels). -Please contact us in the `#meetups-admin` channel on [the community Discord channel](/community/#official-channels). +Please contact us in the `#locals` channel on [the community Discord channel](/community/#official-channels). ## Technical Issues diff --git a/src/pages/community/resources/more-resources.mdx b/src/pages/community/resources/more-resources.mdx index 996b9ea8e3..a663ec1b1c 100644 --- a/src/pages/community/resources/more-resources.mdx +++ b/src/pages/community/resources/more-resources.mdx @@ -20,7 +20,7 @@ If you want to go on a GraphQL meetup you can check out the If you are interested in hosting a GraphQL meetup, The GraphQL Foundation is happy to promote your GraphQL event through the official communication channels. -Please contact us in the `#meetups-admin` channel on +Please contact us in the `#locals` channel on [the community Discord channel](/community/#official-channels). ## GraphQL Logo & Trademark From 4906555599e0be6e2a333691783a04cdc6a8b64a Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 15:20:48 +0100 Subject: [PATCH 008/133] Add Get Your Meetup Noticed section --- src/app/(main)/community/events/page.tsx | 143 +++++++++++------------ 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index 7e4a69b246..0cf1d48dab 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -15,6 +15,7 @@ import Mailbox from "./mailbox.svg?svgr" import { Meetups } from "./meetups" import { BenefitCard } from "./benefit-card" import { EventsScrollview } from "./events-scrollview" +import { Button } from "../../../conf/_design-system/button" const { pastEvents, upcomingEvents } = events.reduce( (acc, event) => { @@ -33,9 +34,10 @@ const { pastEvents, upcomingEvents } = events.reduce( }, ) -export function Events({ events }: { events: Event[] }) { +export function EventsList({ events }: { events: Event[] }) { if (events.length === 0) return null + // TODO: Filters over kind return ( {events.map(event => ( @@ -80,93 +82,37 @@ export default function EventsPage() { />
-
-
-
-

- Submit your meetup -

-
-

- Planning to host a GraphQL meetup? The GraphQL Foundation can - help spread the word through official channels. -

-

- To submit your event, join our{" "} - - Discord - {" "} - and share details in the #meetups-admin channel. -

-
-
- - Go to Discord - -
-
-
-
- -
-
-
-
- {upcomingEvents.length > 0 && (
-

Upcoming Events

- +

Upcoming events

+

+ See what’s coming up across the GraphQL ecosystem. +

+
)}
-

Past Events

- +

Past events

+

+ A look back at how the GraphQL community connects and grows together. +

+

Meetups

-

+

If you are interested in hosting a GraphQL meetup, The GraphQL - Foundation is happy to promote your GraphQL event through the{" "} - - official communication channels - - . + Foundation is happy to promote your GraphQL event through the official + communication channels. .

-

- Please contact us in the #meetups-admin channel on{" "} - - the community Discord channel - - . -

- -
- - Start a GraphQL Local! - -
-
+
) } @@ -178,7 +124,7 @@ function BenefitsSection() {

Benefits of getting involved

-

+

Contributing to GraphQL means more than writing code — it’s a chance to collaborate, share ideas, and shape the future of the ecosystem.

@@ -209,3 +155,56 @@ function BenefitsSection() { ) } + +function GetYourMeetupNoticedSection() { + const serverLink = "https://discord.graphql.org" + const channelLink = + "https://discord.com/channels/625400653321076807/1020000211927576766/" + + return ( +
+
+
+

+ Get your meetup noticed +

+
+

+ Planning to host a GraphQL meetup? The GraphQL Foundation can help + spread the word through official channels. +

+

+ To submit your event, join our{" "} + + Discord + {" "} + and share details in the{" "} + + #locals + {" "} + channel. +

+
+ +
+
+
+ +
+
+
+
+ ) +} From f2b01a1e569f2e90a5a73a6172931100632d8601 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 15:21:16 +0100 Subject: [PATCH 009/133] Initialize Leaflet once to avoid noisy errors --- src/app/(main)/community/events/meetups.tsx | 43 ++++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups.tsx index b994ed4a33..815feab044 100644 --- a/src/app/(main)/community/events/meetups.tsx +++ b/src/app/(main)/community/events/meetups.tsx @@ -1,36 +1,59 @@ -import { useEffect } from "react" +import { useEffect, useRef } from "react" import "leaflet/dist/leaflet.css" import { EventsScrollview } from "./events-scrollview" import { meetups } from "../../../../components/meetups" import { EventCard } from "./event-card" -import pinkCircle from "./pink-cirle.svg" +import pinkCircle from "./pink-circle.svg" export function Meetups() { + const mapContainer = useRef(null) + const mapRef = useRef() + const loadingTokenRef = useRef() + useEffect(() => { - // Load only on client - import("leaflet").then(L => { + if (mapRef.current) return + const loadingToken = (loadingTokenRef.current = Symbol()) + ;(async function loadMap() { + if (!mapContainer.current) return + const Leaflet = await import("leaflet") + + if (loadingToken !== loadingTokenRef.current) { + return + } + // Fixes GET http://localhost:3000/community/upcoming-events/marker-icon-2x.png 404 (Not Found) // and replace default marker image - L.Icon.Default.mergeOptions({ + Leaflet.Icon.Default.mergeOptions({ iconRetinaUrl: pinkCircle.src, shadowUrl: "", }) - const map = L.map("map").setView([45, -15], 2) - L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map) + + const map = Leaflet.map(mapContainer.current).setView([45, -15], 2) + mapRef.current = map + + Leaflet.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo( + map, + ) + for (const { node } of meetups) { - L.marker([node.latitude, node.longitude]) + Leaflet.marker([node.latitude, node.longitude]) .addTo(map) .bindPopup( `${node.name}`, ) } - }) + })() + + return () => { + if (mapRef.current) mapRef.current.remove() + mapRef.current = undefined + } }, []) return ( <> -
+
{meetups.map(({ node }) => ( Date: Fri, 7 Nov 2025 15:32:49 +0100 Subject: [PATCH 010/133] Update to colors based the new design version --- src/app/(main)/community/events/benefit-card.tsx | 2 +- src/app/(main)/community/events/event-card.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/(main)/community/events/benefit-card.tsx b/src/app/(main)/community/events/benefit-card.tsx index 39c987199a..3d35cdde61 100644 --- a/src/app/(main)/community/events/benefit-card.tsx +++ b/src/app/(main)/community/events/benefit-card.tsx @@ -14,7 +14,7 @@ export function BenefitCard({ {icon}

{title}

-

{description}

+

{description}

) diff --git a/src/app/(main)/community/events/event-card.tsx b/src/app/(main)/community/events/event-card.tsx index 110daaaf54..c5f198833d 100644 --- a/src/app/(main)/community/events/event-card.tsx +++ b/src/app/(main)/community/events/event-card.tsx @@ -92,7 +92,10 @@ export function EventCard({ Official GraphQL Local )} {official ? ( - + @@ -116,8 +119,8 @@ export function EventCard({ )} > {dateLabel && ( -
- +
+ {parsedDate ? ( ) : ( @@ -126,8 +129,8 @@ export function EventCard({
)} {city && ( -
- +
+ {city}
)} From 17a029db2539e5c58a485592ce1fac62118123e8 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 15:33:08 +0100 Subject: [PATCH 011/133] Extract EventsList, write todos --- .../(main)/community/events/events-list.tsx | 25 +++++++++++++++++++ .../community/events/events-scrollview.tsx | 4 +-- src/app/(main)/community/events/page.tsx | 25 ++----------------- 3 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 src/app/(main)/community/events/events-list.tsx diff --git a/src/app/(main)/community/events/events-list.tsx b/src/app/(main)/community/events/events-list.tsx new file mode 100644 index 0000000000..714082900f --- /dev/null +++ b/src/app/(main)/community/events/events-list.tsx @@ -0,0 +1,25 @@ +import { EventCard } from "./event-card" +import { EventsScrollview } from "./events-scrollview" +import type { Event } from "./events" + +export function EventsList({ events }: { events: Event[] }) { + if (events.length === 0) return null + + // TODO: Filters over kind (meetup, conference, working-group) + // Show filters only for events already in the list + // Show filters only if there are more than 3 events + return ( + + {events.map(event => ( + + ))} + + ) +} diff --git a/src/app/(main)/community/events/events-scrollview.tsx b/src/app/(main)/community/events/events-scrollview.tsx index d6897dbc17..68fe8edad7 100644 --- a/src/app/(main)/community/events/events-scrollview.tsx +++ b/src/app/(main)/community/events/events-scrollview.tsx @@ -1,8 +1,8 @@ -import { ReactNode } from "react"; +import { ReactNode } from "react" export function EventsScrollview({ children }: { children: ReactNode }) { return ( -
+
{children}
) diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index 0cf1d48dab..ebd1b1d714 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -1,9 +1,8 @@ "use client" -import Link from "next/link" import type { Event } from "./events" -import { events, EventCard } from "./events" +import { events } from "./events" import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" import UsersIcon from "@/app/conf/_design-system/pixelarticons/users.svg?svgr" @@ -14,7 +13,7 @@ import EyeIcon from "@/app/conf/_design-system/pixelarticons/eye.svg?svgr" import Mailbox from "./mailbox.svg?svgr" import { Meetups } from "./meetups" import { BenefitCard } from "./benefit-card" -import { EventsScrollview } from "./events-scrollview" +import { EventsList } from "./events-list" import { Button } from "../../../conf/_design-system/button" const { pastEvents, upcomingEvents } = events.reduce( @@ -34,26 +33,6 @@ const { pastEvents, upcomingEvents } = events.reduce( }, ) -export function EventsList({ events }: { events: Event[] }) { - if (events.length === 0) return null - - // TODO: Filters over kind - return ( - - {events.map(event => ( - - ))} - - ) -} - export default function EventsPage() { return (
From f2590abf6d856561104d23c492ef9f0396a5f436 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 16:13:24 +0100 Subject: [PATCH 012/133] Try Maptiler --- package.json | 1 + pnpm-lock.yaml | 242 ++++++++++++++++++ .../(main)/community/events/events-list.tsx | 48 ++++ src/app/(main)/community/events/meetups.tsx | 10 +- 4 files changed, 298 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 51054343fa..ad531435ac 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@headlessui/react": "^2.2.4", "@igorkowalczyk/is-browser": "^5.1.0", "@lezer/highlight": "^1.2.1", + "@maptiler/leaflet-maptilersdk": "^4.1.1", "@next/bundle-analyzer": "^15.4.5", "@plaiceholder/next": "^3.0.0", "@sparticuz/chromium": "^138.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39e5451429..c256fbe65b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ importers: '@lezer/highlight': specifier: ^1.2.1 version: 1.2.3 + '@maptiler/leaflet-maptilersdk': + specifier: ^4.1.1 + version: 4.1.1 '@next/bundle-analyzer': specifier: ^15.4.5 version: 15.5.6 @@ -1546,6 +1549,46 @@ packages: '@lezer/lr@1.4.2': resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + '@mapbox/geojson-rewind@0.5.2': + resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==} + hasBin: true + + '@mapbox/jsonlint-lines-primitives@2.0.2': + resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==} + engines: {node: '>= 0.6'} + + '@mapbox/point-geometry@1.1.0': + resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==} + + '@mapbox/tiny-sdf@2.0.7': + resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==} + + '@mapbox/unitbezier@0.0.1': + resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} + + '@mapbox/vector-tile@2.0.4': + resolution: {integrity: sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==} + + '@mapbox/whoots-js@3.1.0': + resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} + engines: {node: '>=6.0.0'} + + '@maplibre/maplibre-gl-style-spec@23.3.0': + resolution: {integrity: sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==} + hasBin: true + + '@maplibre/vt-pbf@4.0.3': + resolution: {integrity: sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==} + + '@maptiler/client@2.5.1': + resolution: {integrity: sha512-2rveqohOu3xv16EX65rFUQF8fpPppefrGkvUEtZOx50EDHFxrhEVjKM0p95TuT0RLygxF/sw4vAqUFCBOnPbiw==} + + '@maptiler/leaflet-maptilersdk@4.1.1': + resolution: {integrity: sha512-vCE0K9mlVRyAJkWYOngiA1ZEI42eoWvZd21HLYZE/IU1T+OQc4K1bPoap/z0myAXDLxIJ4wlz+vchr5tKgo0uA==} + + '@maptiler/sdk@3.8.0': + resolution: {integrity: sha512-oXLlSyJhKADB1KTfxj5bhnRml3nrvBerhuuo1v+rDJKrUHqQpVPj6PS+z809D/+UB9ZwzzHOBeFZP3vYCK3U5g==} + '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} @@ -2107,6 +2150,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson-vt@3.2.5': + resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -2161,6 +2207,9 @@ packages: '@types/string-similarity@4.0.2': resolution: {integrity: sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==} + '@types/supercluster@7.1.3': + resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} + '@types/supports-color@8.1.3': resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} @@ -2992,6 +3041,9 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + earcut@3.0.2: + resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3204,6 +3256,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3347,6 +3403,9 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + geojson-vt@4.0.2: + resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -3355,6 +3414,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -3369,6 +3432,9 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -3864,6 +3930,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3910,6 +3979,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-pretty-compact@4.0.0: + resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3923,6 +3995,9 @@ packages: resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} hasBin: true + kdbush@4.0.2: + resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -4053,6 +4128,10 @@ packages: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} + maplibre-gl@5.6.2: + resolution: {integrity: sha512-SEqYThhUCFf6Lm0TckpgpKnto5u4JsdPYdFJb6g12VtuaFsm3nYdBO+fOmnUYddc8dXihgoGnuXvPPooUcRv5w==} + engines: {node: '>=16.14.0', npm: '>=8.1.0'} + markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -4354,6 +4433,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + murmurhash-js@1.0.0: + resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -4651,6 +4733,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pbf@4.0.1: + resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} + hasBin: true + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4768,6 +4854,9 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + potpack@2.1.0: + resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4873,6 +4962,9 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + protocol-buffers-schema@3.6.0: + resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} + prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -4889,6 +4981,13 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@7.3.0: + resolution: {integrity: sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==} + engines: {node: '>=18'} + + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + ranges-apply@7.0.31: resolution: {integrity: sha512-J/METHTxhQTRpLS3hzkvipyRyheAYmAa6BeZaJaTTutIU4spGfU8vKBnhSgKa+WAVAqpZKzqcX29+HHR2lcKLg==} engines: {node: '>=14.18.0'} @@ -5079,6 +5178,9 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -5429,6 +5531,9 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supercluster@8.0.1: + resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5504,6 +5609,9 @@ packages: tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyqueue@3.0.0: + resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + title@4.0.1: resolution: {integrity: sha512-xRnPkJx9nvE5MF6LkB5e8QJjE2FW8269wTu/LQdf7zZqBgPly0QJPf/CWAo7srj5so4yXfoLEdCFgurlpi47zg==} hasBin: true @@ -7410,6 +7518,67 @@ snapshots: dependencies: '@lezer/common': 1.2.3 + '@mapbox/geojson-rewind@0.5.2': + dependencies: + get-stream: 6.0.1 + minimist: 1.2.8 + + '@mapbox/jsonlint-lines-primitives@2.0.2': {} + + '@mapbox/point-geometry@1.1.0': {} + + '@mapbox/tiny-sdf@2.0.7': {} + + '@mapbox/unitbezier@0.0.1': {} + + '@mapbox/vector-tile@2.0.4': + dependencies: + '@mapbox/point-geometry': 1.1.0 + '@types/geojson': 7946.0.16 + pbf: 4.0.1 + + '@mapbox/whoots-js@3.1.0': {} + + '@maplibre/maplibre-gl-style-spec@23.3.0': + dependencies: + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/unitbezier': 0.0.1 + json-stringify-pretty-compact: 4.0.0 + minimist: 1.2.8 + quickselect: 3.0.0 + rw: 1.3.3 + tinyqueue: 3.0.0 + + '@maplibre/vt-pbf@4.0.3': + dependencies: + '@mapbox/point-geometry': 1.1.0 + '@mapbox/vector-tile': 2.0.4 + '@types/geojson-vt': 3.2.5 + '@types/supercluster': 7.1.3 + geojson-vt: 4.0.2 + pbf: 4.0.1 + supercluster: 8.0.1 + + '@maptiler/client@2.5.1': + dependencies: + quick-lru: 7.3.0 + + '@maptiler/leaflet-maptilersdk@4.1.1': + dependencies: + '@maptiler/sdk': 3.8.0 + '@types/leaflet': 1.9.21 + leaflet: 1.9.4 + + '@maptiler/sdk@3.8.0': + dependencies: + '@maplibre/maplibre-gl-style-spec': 23.3.0 + '@maptiler/client': 2.5.1 + events: 3.3.0 + gl-matrix: 3.4.4 + js-base64: 3.7.8 + maplibre-gl: 5.6.2 + uuid: 11.1.0 + '@marijn/find-cluster-break@1.0.2': {} '@mdx-js/mdx@3.1.0(acorn@8.15.0)': @@ -8049,6 +8218,10 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson-vt@3.2.5': + dependencies: + '@types/geojson': 7946.0.16 + '@types/geojson@7946.0.16': {} '@types/hast@3.0.4': @@ -8106,6 +8279,10 @@ snapshots: '@types/string-similarity@4.0.2': {} + '@types/supercluster@7.1.3': + dependencies: + '@types/geojson': 7946.0.16 + '@types/supports-color@8.1.3': {} '@types/tern@0.23.9': @@ -8999,6 +9176,8 @@ snapshots: duplexer@0.1.2: {} + earcut@3.0.2: {} + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.207: {} @@ -9373,6 +9552,8 @@ snapshots: esutils@2.0.3: {} + events@3.3.0: {} + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -9511,6 +9692,8 @@ snapshots: gensync@1.0.0-beta.2: {} + geojson-vt@4.0.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -9529,6 +9712,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-symbol-description@1.1.0: @@ -9543,6 +9728,8 @@ snapshots: github-slugger@2.0.0: {} + gl-matrix@3.4.4: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -10116,6 +10303,8 @@ snapshots: jiti@2.6.1: {} + js-base64@3.7.8: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -10168,6 +10357,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-pretty-compact@4.0.0: {} + json5@2.2.3: {} jsx-ast-utils@3.3.5: @@ -10181,6 +10372,8 @@ snapshots: dependencies: commander: 8.3.0 + kdbush@4.0.2: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -10301,6 +10494,31 @@ snapshots: semver: 5.7.2 optional: true + maplibre-gl@5.6.2: + dependencies: + '@mapbox/geojson-rewind': 0.5.2 + '@mapbox/jsonlint-lines-primitives': 2.0.2 + '@mapbox/point-geometry': 1.1.0 + '@mapbox/tiny-sdf': 2.0.7 + '@mapbox/unitbezier': 0.0.1 + '@mapbox/vector-tile': 2.0.4 + '@mapbox/whoots-js': 3.1.0 + '@maplibre/maplibre-gl-style-spec': 23.3.0 + '@maplibre/vt-pbf': 4.0.3 + '@types/geojson': 7946.0.16 + '@types/geojson-vt': 3.2.5 + '@types/supercluster': 7.1.3 + earcut: 3.0.2 + geojson-vt: 4.0.2 + gl-matrix: 3.4.4 + kdbush: 4.0.2 + murmurhash-js: 1.0.0 + pbf: 4.0.1 + potpack: 2.1.0 + quickselect: 3.0.0 + supercluster: 8.0.1 + tinyqueue: 3.0.0 + markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -10895,6 +11113,8 @@ snapshots: ms@2.1.3: {} + murmurhash-js@1.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -11271,6 +11491,10 @@ snapshots: pathe@2.0.3: {} + pbf@4.0.1: + dependencies: + resolve-protobuf-schema: 2.1.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -11377,6 +11601,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + potpack@2.1.0: {} + prelude-ls@1.2.1: {} prettier-plugin-pkg@0.21.2(prettier@3.5.3): @@ -11414,6 +11640,8 @@ snapshots: property-information@7.1.0: {} + protocol-buffers-schema@3.6.0: {} + prr@1.0.1: optional: true @@ -11428,6 +11656,10 @@ snapshots: queue-microtask@1.2.3: {} + quick-lru@7.3.0: {} + + quickselect@3.0.0: {} + ranges-apply@7.0.31: dependencies: ranges-merge: 9.0.30 @@ -11729,6 +11961,10 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve-protobuf-schema@2.1.0: + dependencies: + protocol-buffers-schema: 3.6.0 + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -12167,6 +12403,10 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + supercluster@8.0.1: + dependencies: + kdbush: 4.0.2 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -12269,6 +12509,8 @@ snapshots: tinyexec@1.0.1: {} + tinyqueue@3.0.0: {} + title@4.0.1: dependencies: arg: 5.0.2 diff --git a/src/app/(main)/community/events/events-list.tsx b/src/app/(main)/community/events/events-list.tsx index 714082900f..3d62ce96a7 100644 --- a/src/app/(main)/community/events/events-list.tsx +++ b/src/app/(main)/community/events/events-list.tsx @@ -1,7 +1,55 @@ +import type { ComponentPropsWithoutRef } from "react" +import { clsx } from "clsx" + import { EventCard } from "./event-card" import { EventsScrollview } from "./events-scrollview" import type { Event } from "./events" +interface FilterChipProps extends ComponentPropsWithoutRef<"button"> { + active?: boolean + count?: number +} + +export function FilterChip({ + active = false, + children, + className, + count, + disabled, + type, + ...props +}: FilterChipProps) { + const showCount = typeof count === "number" + + return ( + + ) +} + export function EventsList({ events }: { events: Event[] }) { if (events.length === 0) return null diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups.tsx index 815feab044..91fd7ec8ff 100644 --- a/src/app/(main)/community/events/meetups.tsx +++ b/src/app/(main)/community/events/meetups.tsx @@ -5,6 +5,8 @@ import { EventsScrollview } from "./events-scrollview" import { meetups } from "../../../../components/meetups" import { EventCard } from "./event-card" +import { MaptilerLayer } from "@maptiler/leaflet-maptilersdk" + import pinkCircle from "./pink-circle.svg" export function Meetups() { @@ -33,9 +35,11 @@ export function Meetups() { const map = Leaflet.map(mapContainer.current).setView([45, -15], 2) mapRef.current = map - Leaflet.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo( - map, - ) + new MaptilerLayer({ + apiKey: "TXh3zyr74vOlxKSUwkgO", + style: + "https://api.maptiler.com/maps/019a5ead-6001-7646-ae0f-997316795f2d/style.json?key=TXh3zyr74vOlxKSUwkgO", + }).addTo(map) for (const { node } of meetups) { Leaflet.marker([node.latitude, node.longitude]) From acd760f543758e438721b2d3adbfe9f35133d109 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 7 Nov 2025 17:08:37 +0100 Subject: [PATCH 013/133] wip --- src/app/(main)/community/events/event-card.tsx | 2 +- src/app/(main)/community/events/meetups.tsx | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/app/(main)/community/events/event-card.tsx b/src/app/(main)/community/events/event-card.tsx index c5f198833d..6020c0062c 100644 --- a/src/app/(main)/community/events/event-card.tsx +++ b/src/app/(main)/community/events/event-card.tsx @@ -94,7 +94,7 @@ export function EventCard({ {official ? ( ★ diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups.tsx index 91fd7ec8ff..fc076c62a6 100644 --- a/src/app/(main)/community/events/meetups.tsx +++ b/src/app/(main)/community/events/meetups.tsx @@ -1,13 +1,12 @@ -import { useEffect, useRef } from "react" +import { useEffect, useId, useRef } from "react" import "leaflet/dist/leaflet.css" import { EventsScrollview } from "./events-scrollview" import { meetups } from "../../../../components/meetups" import { EventCard } from "./event-card" -import { MaptilerLayer } from "@maptiler/leaflet-maptilersdk" - import pinkCircle from "./pink-circle.svg" +import { PixelateFilter } from "./pixelate-filter" export function Meetups() { const mapContainer = useRef(null) @@ -35,11 +34,9 @@ export function Meetups() { const map = Leaflet.map(mapContainer.current).setView([45, -15], 2) mapRef.current = map - new MaptilerLayer({ - apiKey: "TXh3zyr74vOlxKSUwkgO", - style: - "https://api.maptiler.com/maps/019a5ead-6001-7646-ae0f-997316795f2d/style.json?key=TXh3zyr74vOlxKSUwkgO", - }).addTo(map) + Leaflet.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo( + map, + ) for (const { node } of meetups) { Leaflet.marker([node.latitude, node.longitude]) From 2693ab76fabc07ae39e900d16182533ec9f55873 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 13:07:26 +0100 Subject: [PATCH 014/133] Show both events and meetups in the same lists --- .../(main)/community/events/events-list.tsx | 35 +++++++++++------- src/app/(main)/community/events/events.ts | 4 +++ src/app/(main)/community/events/links.tsx | 4 +++ src/app/(main)/community/events/meetups.tsx | 3 +- src/app/(main)/community/events/page.tsx | 36 +++++++++---------- 5 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 src/app/(main)/community/events/links.tsx diff --git a/src/app/(main)/community/events/events-list.tsx b/src/app/(main)/community/events/events-list.tsx index 3d62ce96a7..e4f3d3783b 100644 --- a/src/app/(main)/community/events/events-list.tsx +++ b/src/app/(main)/community/events/events-list.tsx @@ -3,7 +3,7 @@ import { clsx } from "clsx" import { EventCard } from "./event-card" import { EventsScrollview } from "./events-scrollview" -import type { Event } from "./events" +import type { Event, Meetup } from "./events" interface FilterChipProps extends ComponentPropsWithoutRef<"button"> { active?: boolean @@ -50,7 +50,7 @@ export function FilterChip({ ) } -export function EventsList({ events }: { events: Event[] }) { +export function EventsList({ events }: { events: Array }) { if (events.length === 0) return null // TODO: Filters over kind (meetup, conference, working-group) @@ -58,16 +58,27 @@ export function EventsList({ events }: { events: Event[] }) { // Show filters only if there are more than 3 events return ( - {events.map(event => ( - - ))} + {events.map(event => + "node" in event ? ( + + ) : ( + + ), + )} ) } diff --git a/src/app/(main)/community/events/events.ts b/src/app/(main)/community/events/events.ts index 13805a111d..1246fad931 100644 --- a/src/app/(main)/community/events/events.ts +++ b/src/app/(main)/community/events/events.ts @@ -158,3 +158,7 @@ export const events: Event[] = [ hostLink: "https://www.truedigitalpark.com/", }, ].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + +import type { meetups } from "@/components/meetups" + +export type Meetup = (typeof meetups)[number] diff --git a/src/app/(main)/community/events/links.tsx b/src/app/(main)/community/events/links.tsx new file mode 100644 index 0000000000..4628dd394a --- /dev/null +++ b/src/app/(main)/community/events/links.tsx @@ -0,0 +1,4 @@ +export const DISCORD_SERVER_LINK = "https://discord.graphql.org" + +export const DISCORD_CHANNEL_LINK = + "https://discord.com/channels/625400653321076807/1020000211927576766/" diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups.tsx index fc076c62a6..815feab044 100644 --- a/src/app/(main)/community/events/meetups.tsx +++ b/src/app/(main)/community/events/meetups.tsx @@ -1,4 +1,4 @@ -import { useEffect, useId, useRef } from "react" +import { useEffect, useRef } from "react" import "leaflet/dist/leaflet.css" import { EventsScrollview } from "./events-scrollview" @@ -6,7 +6,6 @@ import { meetups } from "../../../../components/meetups" import { EventCard } from "./event-card" import pinkCircle from "./pink-circle.svg" -import { PixelateFilter } from "./pixelate-filter" export function Meetups() { const mapContainer = useRef(null) diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index ebd1b1d714..3c44480d82 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -1,7 +1,5 @@ "use client" -import type { Event } from "./events" - import { events } from "./events" import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" @@ -15,6 +13,10 @@ import { Meetups } from "./meetups" import { BenefitCard } from "./benefit-card" import { EventsList } from "./events-list" import { Button } from "../../../conf/_design-system/button" +import { DISCORD_CHANNEL_LINK, DISCORD_SERVER_LINK } from "./links" + +import { meetups } from "@/components/meetups" +import { type Event, type Meetup } from "./events" const { pastEvents, upcomingEvents } = events.reduce( (acc, event) => { @@ -33,6 +35,9 @@ const { pastEvents, upcomingEvents } = events.reduce( }, ) + +const pastEventsAndMeetups: Array = [...pastEvents, ...meetups] + export default function EventsPage() { return (
@@ -72,22 +77,21 @@ export default function EventsPage() { )}
-

Past events

+

Meetups

- A look back at how the GraphQL community connects and grows together. + Find and join local GraphQL meetups happening around the world. Select + a city to explore upcoming events.

- + +
-

Meetups

+

Past events & meetups

- If you are interested in hosting a GraphQL meetup, The GraphQL - Foundation is happy to promote your GraphQL event through the official - communication channels. . + A look back at how the GraphQL community connects and grows together.

- - +
@@ -136,10 +140,6 @@ function BenefitsSection() { } function GetYourMeetupNoticedSection() { - const serverLink = "https://discord.graphql.org" - const channelLink = - "https://discord.com/channels/625400653321076807/1020000211927576766/" - return (
@@ -155,7 +155,7 @@ function GetYourMeetupNoticedSection() {

To submit your event, join our{" "} {" "} and share details in the{" "}

-
From 79ee56362b70ab8f349a56a87d89a1b997e530d9 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 13:08:30 +0100 Subject: [PATCH 015/133] Remove a scrollview from the MeetupsMap --- src/app/(main)/community/events/meetups.tsx | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups.tsx index 815feab044..cd62d50c7a 100644 --- a/src/app/(main)/community/events/meetups.tsx +++ b/src/app/(main)/community/events/meetups.tsx @@ -1,9 +1,7 @@ import { useEffect, useRef } from "react" import "leaflet/dist/leaflet.css" -import { EventsScrollview } from "./events-scrollview" import { meetups } from "../../../../components/meetups" -import { EventCard } from "./event-card" import pinkCircle from "./pink-circle.svg" @@ -51,21 +49,6 @@ export function Meetups() { mapRef.current = undefined } }, []) - return ( - <> -
- - {meetups.map(({ node }) => ( - - ))} - - - ) + + return
} From 6d6c2c35c0971e379a6167f2192b1d2411fbba66 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 13:12:21 +0100 Subject: [PATCH 016/133] Rename the component --- .../(main)/community/events/{meetups.tsx => meetups-map.tsx} | 2 +- src/app/(main)/community/events/page.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename src/app/(main)/community/events/{meetups.tsx => meetups-map.tsx} (98%) diff --git a/src/app/(main)/community/events/meetups.tsx b/src/app/(main)/community/events/meetups-map.tsx similarity index 98% rename from src/app/(main)/community/events/meetups.tsx rename to src/app/(main)/community/events/meetups-map.tsx index cd62d50c7a..d0771743c6 100644 --- a/src/app/(main)/community/events/meetups.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -5,7 +5,7 @@ import { meetups } from "../../../../components/meetups" import pinkCircle from "./pink-circle.svg" -export function Meetups() { +export function MeetupsMap() { const mapContainer = useRef(null) const mapRef = useRef() const loadingTokenRef = useRef() diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index 3c44480d82..383849f62e 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -9,7 +9,7 @@ import SlidersIcon from "@/app/conf/_design-system/pixelarticons/sliders.svg?svg import EyeIcon from "@/app/conf/_design-system/pixelarticons/eye.svg?svgr" import Mailbox from "./mailbox.svg?svgr" -import { Meetups } from "./meetups" +import { MeetupsMap } from "./meetups-map" import { BenefitCard } from "./benefit-card" import { EventsList } from "./events-list" import { Button } from "../../../conf/_design-system/button" @@ -35,7 +35,6 @@ const { pastEvents, upcomingEvents } = events.reduce( }, ) - const pastEventsAndMeetups: Array = [...pastEvents, ...meetups] export default function EventsPage() { @@ -83,7 +82,7 @@ export default function EventsPage() { a city to explore upcoming events.

- +
From fc47e2490a087b1043b0d5fa0525a79bb0632699 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 14:03:35 +0100 Subject: [PATCH 017/133] Add Bring GraphQL to Your Community --- .../community/events/benefits-section.tsx | 45 +++++++ .../bring-graphql-to-your-community.tsx | 50 ++++++++ .../get-your-meetup-noticed-section.tsx | 52 +++++++++ src/app/(main)/community/events/page.tsx | 110 ++---------------- 4 files changed, 155 insertions(+), 102 deletions(-) create mode 100644 src/app/(main)/community/events/benefits-section.tsx create mode 100644 src/app/(main)/community/events/bring-graphql-to-your-community.tsx create mode 100644 src/app/(main)/community/events/get-your-meetup-noticed-section.tsx diff --git a/src/app/(main)/community/events/benefits-section.tsx b/src/app/(main)/community/events/benefits-section.tsx new file mode 100644 index 0000000000..bb0d333bd9 --- /dev/null +++ b/src/app/(main)/community/events/benefits-section.tsx @@ -0,0 +1,45 @@ +import UsersIcon from "@/app/conf/_design-system/pixelarticons/users.svg?svgr" +import CommentIcon from "@/app/conf/_design-system/pixelarticons/comment.svg?svgr" +import SlidersIcon from "@/app/conf/_design-system/pixelarticons/sliders.svg?svgr" +import EyeIcon from "@/app/conf/_design-system/pixelarticons/eye.svg?svgr" + +import { BenefitCard } from "./benefit-card" + +export function BenefitsSection() { + return ( +
+
+

+ Benefits of getting involved +

+

+ Contributing to GraphQL means more than writing code — it’s a chance + to collaborate, share ideas, and shape the future of the ecosystem. +

+
+ +
+ } + title="Valuable networking opportunities" + description="Engage in conversations and hands-on projects to deepen your understanding of GraphQL." + /> + } + title="Collaborate with others" + description="Connect with contributors and teams building GraphQL tools and platforms." + /> + } + title="Help guide the spec" + description="Share ideas, give feedback, or participate in working groups to influence the future of GraphQL." + /> + } + title="Connect in real life" + description="Put a face to the handle — meet contributors in person at events and meetups. Build lasting connections beyond the screen." + /> +
+
+ ) +} diff --git a/src/app/(main)/community/events/bring-graphql-to-your-community.tsx b/src/app/(main)/community/events/bring-graphql-to-your-community.tsx new file mode 100644 index 0000000000..d779490cc5 --- /dev/null +++ b/src/app/(main)/community/events/bring-graphql-to-your-community.tsx @@ -0,0 +1,50 @@ +import { Button } from "../../../conf/_design-system/button" +import { StripesDecoration } from "../../../conf/_design-system/stripes-decoration" +import { DISCORD_CHANNEL_LINK } from "./links" + +export function BringGraphQLToYourCommunity() { + return ( +
+
+ +
+

Bring GraphQL to your community

+

+ Learn how to start a local initiative and create your own – host + events, share knowledge, and grow the GraphQL community where you + live. +

+
+
+ + +
+
+
+ ) +} + +function Stripes() { + return ( +
+ +
+ ) +} diff --git a/src/app/(main)/community/events/get-your-meetup-noticed-section.tsx b/src/app/(main)/community/events/get-your-meetup-noticed-section.tsx new file mode 100644 index 0000000000..1bc8ca672e --- /dev/null +++ b/src/app/(main)/community/events/get-your-meetup-noticed-section.tsx @@ -0,0 +1,52 @@ +import Mailbox from "./mailbox.svg?svgr" +import { Button } from "@/app/conf/_design-system/button" +import { DISCORD_CHANNEL_LINK, DISCORD_SERVER_LINK } from "./links" + +export function GetYourMeetupNoticedSection() { + return ( +
+ +
+ ) +} diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index 383849f62e..196404dce5 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -1,22 +1,14 @@ "use client" -import { events } from "./events" -import { Breadcrumbs } from "../../../../_design-system/breadcrumbs" - -import UsersIcon from "@/app/conf/_design-system/pixelarticons/users.svg?svgr" -import CommentIcon from "@/app/conf/_design-system/pixelarticons/comment.svg?svgr" -import SlidersIcon from "@/app/conf/_design-system/pixelarticons/sliders.svg?svgr" -import EyeIcon from "@/app/conf/_design-system/pixelarticons/eye.svg?svgr" +import { Breadcrumbs } from "@/_design-system/breadcrumbs" +import { meetups } from "@/components/meetups" -import Mailbox from "./mailbox.svg?svgr" import { MeetupsMap } from "./meetups-map" -import { BenefitCard } from "./benefit-card" import { EventsList } from "./events-list" -import { Button } from "../../../conf/_design-system/button" -import { DISCORD_CHANNEL_LINK, DISCORD_SERVER_LINK } from "./links" - -import { meetups } from "@/components/meetups" -import { type Event, type Meetup } from "./events" +import { events, type Event, type Meetup } from "./events" +import { BenefitsSection } from "./benefits-section" +import { GetYourMeetupNoticedSection } from "./get-your-meetup-noticed-section" +import { BringGraphQLToYourCommunity } from "./bring-graphql-to-your-community" const { pastEvents, upcomingEvents } = events.reduce( (acc, event) => { @@ -75,6 +67,8 @@ export default function EventsPage() {
)} + +

Meetups

@@ -98,91 +92,3 @@ export default function EventsPage() {

) } - -function BenefitsSection() { - return ( -
-
-

- Benefits of getting involved -

-

- Contributing to GraphQL means more than writing code — it’s a chance - to collaborate, share ideas, and shape the future of the ecosystem. -

-
- -
- } - title="Valuable networking opportunities" - description="Engage in conversations and hands-on projects to deepen your understanding of GraphQL." - /> - } - title="Collaborate with others" - description="Connect with contributors and teams building GraphQL tools and platforms." - /> - } - title="Help guide the spec" - description="Share ideas, give feedback, or participate in working groups to influence the future of GraphQL." - /> - } - title="Connect in real life" - description="Put a face to the handle — meet contributors in person at events and meetups. Build lasting connections beyond the screen." - /> -
-
- ) -} - -function GetYourMeetupNoticedSection() { - return ( -
-
-
-

- Get your meetup noticed -

-
-

- Planning to host a GraphQL meetup? The GraphQL Foundation can help - spread the word through official channels. -

-

- To submit your event, join our{" "} - - Discord - {" "} - and share details in the{" "} - - #locals - {" "} - channel. -

-
- -
-
-
- -
-
-
-
- ) -} From 10a1bf194af6cd68dabdd58496bbebf51ddc85e7 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 17:36:56 +0100 Subject: [PATCH 018/133] Add event filter tags --- .../bring-graphql-to-your-community.tsx | 5 +- .../(main)/community/events/event-card.tsx | 3 + .../community/events/event-filter-tag.tsx | 29 ++++++ .../(main)/community/events/events-list.tsx | 89 +++++++++++++------ 4 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 src/app/(main)/community/events/event-filter-tag.tsx diff --git a/src/app/(main)/community/events/bring-graphql-to-your-community.tsx b/src/app/(main)/community/events/bring-graphql-to-your-community.tsx index d779490cc5..c2ef49c35d 100644 --- a/src/app/(main)/community/events/bring-graphql-to-your-community.tsx +++ b/src/app/(main)/community/events/bring-graphql-to-your-community.tsx @@ -41,10 +41,7 @@ export function BringGraphQLToYourCommunity() { function Stripes() { return (
- +
) } diff --git a/src/app/(main)/community/events/event-card.tsx b/src/app/(main)/community/events/event-card.tsx index 6020c0062c..d354491a13 100644 --- a/src/app/(main)/community/events/event-card.tsx +++ b/src/app/(main)/community/events/event-card.tsx @@ -55,6 +55,7 @@ export interface EventCardProps { name: ReactNode meta?: ReactNode official?: boolean + kind?: "meetup" | "conference" | "working-group" } export function EventCard({ @@ -64,6 +65,7 @@ export function EventCard({ name, meta, official, + kind }: EventCardProps) { const dateLabel = formatDateLabel(date) const parsedDate = normaliseDate(date) @@ -73,6 +75,7 @@ export function EventCard({ href={href} className={clsx( "gql-focus-visible group flex min-w-[260px] flex-col overflow-hidden border border-neu-200 bg-neu-0 text-left text-current no-underline ring-neu-400 hover:bg-sec-base/[.035] hover:ring-1 hover:ring-offset-1 hover:ring-offset-neu-0 dark:border-neu-100 dark:ring-neu-100 xs:min-w-[352px]", + kind === "meetup" && "bg-[#FFF9FD]" )} target="_blank" rel="noreferrer" diff --git a/src/app/(main)/community/events/event-filter-tag.tsx b/src/app/(main)/community/events/event-filter-tag.tsx new file mode 100644 index 0000000000..5fde980f4f --- /dev/null +++ b/src/app/(main)/community/events/event-filter-tag.tsx @@ -0,0 +1,29 @@ +import { Tag } from "@/app/conf/_design-system/tag" +import { CheckboxIcon } from "@/app/conf/_design-system/pixelarticons/checkbox-icon" + +export type EventKind = "meetup" | "conference" | "working-group" + +const colors = { + meetup: "hsl(var(--color-pri-base))", + conference: "hsl(var(--color-sec-dark))", + "working-group": "#6883FF", +} + +export function EventFilterTag({ + kind, + checked, + onChange, +}: { + kind: EventKind + checked: boolean + onChange: (event: React.ChangeEvent) => void +}) { + return ( + + + + {kind.replace("-", " ")} + + + ) +} diff --git a/src/app/(main)/community/events/events-list.tsx b/src/app/(main)/community/events/events-list.tsx index e4f3d3783b..ea6b9d7397 100644 --- a/src/app/(main)/community/events/events-list.tsx +++ b/src/app/(main)/community/events/events-list.tsx @@ -1,9 +1,10 @@ -import type { ComponentPropsWithoutRef } from "react" +import { useState, type ComponentPropsWithoutRef } from "react" import { clsx } from "clsx" import { EventCard } from "./event-card" import { EventsScrollview } from "./events-scrollview" import type { Event, Meetup } from "./events" +import { EventFilterTag, EventKind } from "./event-filter-tag" interface FilterChipProps extends ComponentPropsWithoutRef<"button"> { active?: boolean @@ -50,35 +51,69 @@ export function FilterChip({ ) } +const ALL_SHOWN = { + meetup: true, + conference: true, + "working-group": true, +} satisfies Record + export function EventsList({ events }: { events: Array }) { + const [kindFilters, setKindFilters] = useState(ALL_SHOWN) + if (events.length === 0) return null - // TODO: Filters over kind (meetup, conference, working-group) - // Show filters only for events already in the list - // Show filters only if there are more than 3 events + const tags: Set = new Set() + events.forEach(event => { + // todo: add working groups + if ("node" in event) tags.add("meetup") + else tags.add("conference") + }) + return ( - - {events.map(event => - "node" in event ? ( - - ) : ( - - ), - )} - +
+ {tags.size > 1 && events.length > 4 ? ( +
+ Event type +
+ {Array.from(tags).map(tag => ( + { + setKindFilters(prev => ({ + ...prev, + [tag]: event.target.checked, + })) + }} + /> + ))} +
+
+ ) : null} + + {events.map(event => + "node" in event ? ( + + ) : ( + + ), + )} + +
) } From 4b40b4f82d796d4a1c6320112bae45b7c382b065 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 18:24:32 +0100 Subject: [PATCH 019/133] Add a mask --- .../events/bring-graphql-to-your-community.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/(main)/community/events/bring-graphql-to-your-community.tsx b/src/app/(main)/community/events/bring-graphql-to-your-community.tsx index c2ef49c35d..053fc49808 100644 --- a/src/app/(main)/community/events/bring-graphql-to-your-community.tsx +++ b/src/app/(main)/community/events/bring-graphql-to-your-community.tsx @@ -39,8 +39,16 @@ export function BringGraphQLToYourCommunity() { } function Stripes() { + const mask = "linear-gradient(20deg, transparent 80%, rgb(0 0 0 / 0.6))" return ( -
+
) From 39759a5fc253c097475b13aadb21f2bf86dec695 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 18:24:50 +0100 Subject: [PATCH 020/133] Filter events --- .../community/events/event-filter-tag.tsx | 37 ++++++++--- .../(main)/community/events/events-list.tsx | 61 +++++++++++-------- src/app/(main)/community/events/page.tsx | 15 ++++- .../pixelarticons/checkbox-icon.tsx | 2 +- 4 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/app/(main)/community/events/event-filter-tag.tsx b/src/app/(main)/community/events/event-filter-tag.tsx index 5fde980f4f..d2ceee05ed 100644 --- a/src/app/(main)/community/events/event-filter-tag.tsx +++ b/src/app/(main)/community/events/event-filter-tag.tsx @@ -1,5 +1,6 @@ import { Tag } from "@/app/conf/_design-system/tag" import { CheckboxIcon } from "@/app/conf/_design-system/pixelarticons/checkbox-icon" +import clsx from "clsx" export type EventKind = "meetup" | "conference" | "working-group" @@ -9,21 +10,39 @@ const colors = { "working-group": "#6883FF", } +export interface EventFilterTagProps + extends Omit, "onChange"> { + kind: EventKind + checked: boolean + onChange: (event: React.ChangeEvent) => void +} + export function EventFilterTag({ kind, checked, onChange, -}: { - kind: EventKind - checked: boolean - onChange: (event: React.ChangeEvent) => void -}) { + ...rest +}: EventFilterTagProps) { return ( - - - + ) } diff --git a/src/app/(main)/community/events/events-list.tsx b/src/app/(main)/community/events/events-list.tsx index ea6b9d7397..d288e41946 100644 --- a/src/app/(main)/community/events/events-list.tsx +++ b/src/app/(main)/community/events/events-list.tsx @@ -57,7 +57,13 @@ const ALL_SHOWN = { "working-group": true, } satisfies Record -export function EventsList({ events }: { events: Array }) { +export function EventsList({ + events, + className, +}: { + events: Array + className?: string +}) { const [kindFilters, setKindFilters] = useState(ALL_SHOWN) if (events.length === 0) return null @@ -70,11 +76,11 @@ export function EventsList({ events }: { events: Array }) { }) return ( -
+
{tags.size > 1 && events.length > 4 ? (
- Event type -
+ Event type +
{Array.from(tags).map(tag => ( }) {
) : null} - {events.map(event => - "node" in event ? ( - - ) : ( - - ), - )} + {events + .filter(event => { + if ("node" in event) return kindFilters["meetup"] + else return kindFilters["conference"] + }) + .map(event => + "node" in event ? ( + + ) : ( + + ), + )}
) diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index 196404dce5..5c1de46efd 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -27,7 +27,16 @@ const { pastEvents, upcomingEvents } = events.reduce( }, ) -const pastEventsAndMeetups: Array = [...pastEvents, ...meetups] +const pastEventsAndMeetups: Array = [ + ...pastEvents, + ...meetups, +].sort((a, b) => { + const dateA = + "node" in a ? new Date(a.node.next || a.node.prev) : new Date(a.date) + const dateB = + "node" in b ? new Date(b.node.next || b.node.prev) : new Date(b.date) + return dateB.getTime() - dateA.getTime() +}) export default function EventsPage() { return ( @@ -60,7 +69,7 @@ export default function EventsPage() { {upcomingEvents.length > 0 && (

Upcoming events

-

+

See what’s coming up across the GraphQL ecosystem.

@@ -81,7 +90,7 @@ export default function EventsPage() {

Past events & meetups

-

+

A look back at how the GraphQL community connects and grows together.

diff --git a/src/app/conf/_design-system/pixelarticons/checkbox-icon.tsx b/src/app/conf/_design-system/pixelarticons/checkbox-icon.tsx index 7be08f9281..b0fe637f37 100644 --- a/src/app/conf/_design-system/pixelarticons/checkbox-icon.tsx +++ b/src/app/conf/_design-system/pixelarticons/checkbox-icon.tsx @@ -21,7 +21,7 @@ export function CheckboxIcon({ checked, ...rest }: CheckboxIconProps) { ) : ( - + From 8dd424ecec2f715675121322b001ab81abcda33b Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Sun, 9 Nov 2025 19:30:20 +0100 Subject: [PATCH 021/133] Fix colors and spacing --- .../(main)/community/events/event-card.tsx | 46 +++++++++---------- .../community/events/event-filter-tag.tsx | 13 ++++-- .../(main)/community/events/events-list.tsx | 4 +- src/components/navbar/navbar.tsx | 7 ++- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/app/(main)/community/events/event-card.tsx b/src/app/(main)/community/events/event-card.tsx index d354491a13..346bdee5bc 100644 --- a/src/app/(main)/community/events/event-card.tsx +++ b/src/app/(main)/community/events/event-card.tsx @@ -4,6 +4,7 @@ import { clsx } from "clsx" import { CalendarIcon } from "@/app/conf/_design-system/pixelarticons/calendar-icon" import { PinIcon } from "@/app/conf/_design-system/pixelarticons/pin-icon" import { Tag } from "@/app/conf/_design-system/tag" +import { eventTagColors } from "./event-filter-tag" const dateFormatter = new Intl.DateTimeFormat("en", { day: "numeric", @@ -55,7 +56,7 @@ export interface EventCardProps { name: ReactNode meta?: ReactNode official?: boolean - kind?: "meetup" | "conference" | "working-group" + kind: "meetup" | "conference" | "working-group" } export function EventCard({ @@ -65,17 +66,24 @@ export function EventCard({ name, meta, official, - kind + kind, }: EventCardProps) { const dateLabel = formatDateLabel(date) const parsedDate = normaliseDate(date) - return (
+ {kind} {meta ? ( - {meta} + {meta} ) : ( Official GraphQL Local )} - {official ? ( - - - ★ - - Official - - ) : meta ? null : ( -
- )}
@@ -115,15 +111,15 @@ export function EventCard({
{dateLabel && (
- + {parsedDate ? ( ) : ( @@ -133,7 +129,7 @@ export function EventCard({ )} {city && (
- + {city}
)} diff --git a/src/app/(main)/community/events/event-filter-tag.tsx b/src/app/(main)/community/events/event-filter-tag.tsx index d2ceee05ed..9b1ebc398f 100644 --- a/src/app/(main)/community/events/event-filter-tag.tsx +++ b/src/app/(main)/community/events/event-filter-tag.tsx @@ -4,9 +4,9 @@ import clsx from "clsx" export type EventKind = "meetup" | "conference" | "working-group" -const colors = { - meetup: "hsl(var(--color-pri-base))", - conference: "hsl(var(--color-sec-dark))", +export const eventTagColors = { + conference: "hsl(var(--color-pri-base))", + meetup: "hsl(var(--color-sec-dark))", "working-group": "#6883FF", } @@ -34,11 +34,14 @@ export function EventFilterTag({ onChange={onChange} checked={checked} /> - + {kind.replace("-", " ")} diff --git a/src/app/(main)/community/events/events-list.tsx b/src/app/(main)/community/events/events-list.tsx index d288e41946..de5c041bf3 100644 --- a/src/app/(main)/community/events/events-list.tsx +++ b/src/app/(main)/community/events/events-list.tsx @@ -80,7 +80,7 @@ export function EventsList({ {tags.size > 1 && events.length > 4 ? (
Event type -
+
{Array.from(tags).map(tag => ( ) : ( ), )} diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx index 5ecd69dfdf..cb819dfbba 100644 --- a/src/components/navbar/navbar.tsx +++ b/src/components/navbar/navbar.tsx @@ -86,10 +86,9 @@ export function Navbar({ items }: NavbarProps): ReactElement { return (
) + return (
= Object.create(null) @@ -229,7 +224,7 @@ function Separator({ title }: { title: string }): ReactElement { className={cn( "[word-break:break-word]", title - ? "typography-body-sm mb-2 px-2 py-1.5 font-semibold text-neu-800 [&:not(:first-child)]:mt-5" + ? "typography-body-sm mb-2 px-2 py-1.5 font-semibold text-neu-800 max-md:first:hidden [&:not(:first-child)]:mt-5" : "my-4", )} > @@ -370,7 +365,6 @@ export function Sidebar({ const { menu, setMenu } = useMenu() const [focused, setFocused] = useState("") const [showSidebar, setSidebar] = useState(true) - const [showToggleAnimation, setToggleAnimation] = useState(false) const anchors = useMemo(() => toc.filter(v => v.depth === 2), [toc]) const sidebarRef = useRef(null!) @@ -480,7 +474,6 @@ export function Sidebar({ )} @@ -492,17 +485,13 @@ export function Sidebar({ export function SidebarFooter({ showSidebar, setSidebar, - showToggleAnimation = false, hasI18n = false, - setToggleAnimation, className, hiddenOnMobile = true, }: { showSidebar: boolean setSidebar: (show: boolean) => void - showToggleAnimation?: boolean hasI18n?: boolean - setToggleAnimation?: (show: boolean) => void className?: string hiddenOnMobile?: boolean }) { @@ -519,14 +508,7 @@ export function SidebarFooter({ : "flex-col flex-wrap justify-center py-4", className, )} - data-toggle-animation={ - showToggleAnimation ? (showSidebar ? "show" : "hide") : "off" - } > -
@@ -539,7 +521,6 @@ export function SidebarFooter({ )} onClick={() => { setSidebar(!showSidebar) - setToggleAnimation?.(true) }} > { + let state = false + const listeners = new Set<() => void>() + return { + get: () => state, + sub: (l: () => void) => (listeners.add(l), () => listeners.delete(l)), + set: (action: boolean | ((prev: boolean) => boolean)) => { + const val = typeof action === "function" ? action(state) : action + if (val !== state) { + state = val + listeners.forEach(l => l()) + } + }, + } +} + +const store = createStore() + +export const useMenu = () => ({ + menu: useSyncExternalStore(store.sub, store.get, store.get), + setMenu: store.set, +}) diff --git a/src/nextra-theme-docs.css b/src/nextra-theme-docs.css index 83c80ae83c..e497535b71 100644 --- a/src/nextra-theme-docs.css +++ b/src/nextra-theme-docs.css @@ -2958,3 +2958,12 @@ kbd._border._gap-1 { color: hsl(var(--color-neu-500)); } } + +:root { + --nextra-bg: 251, 251, 249; +} +@media (prefers-color-scheme: dark) { + :root { + --nextra-bg: 15, 15, 12; + } +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 5bf5b37f4c..fc634b1f52 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -8,6 +8,8 @@ import "@/docs.css" import "@/globals.css" import "@/app/colors.css" +import { useMenu } from "../components/use-menu" + const gaId = process.env.NEXT_PUBLIC_GA_ID // https://developers.google.com/analytics/devguides/collection/gtagjs/pages @@ -17,14 +19,18 @@ function handleRouteChange(url: string) { export default function App({ Component, pageProps }: AppProps) { const router = useRouter() + useEffect(() => { if (!gaId) return router.events.on("routeChangeComplete", handleRouteChange) return () => { router.events.off("routeChangeComplete", handleRouteChange) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + const menu = useMenu() + return ( <> From f78832d2563663ef4a9c82d1885226f39fd3ae13 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 10 Nov 2025 13:24:41 +0100 Subject: [PATCH 028/133] Fix an unrelated typo --- src/app/conf/2025/components/register-today/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/conf/2025/components/register-today/index.tsx b/src/app/conf/2025/components/register-today/index.tsx index fd423f237a..094448c674 100644 --- a/src/app/conf/2025/components/register-today/index.tsx +++ b/src/app/conf/2025/components/register-today/index.tsx @@ -38,7 +38,7 @@ export function RegisterToday({ className }: RegisterTodayProps) {
-
+
Scroll to zoom, drag to pan, press R to reset
@@ -166,6 +196,9 @@ export function MeetupsMap() { ) } -function clamp(value: number, min: number, max: number) { - return Math.min(max, Math.max(min, value)) +function getMapColors(theme?: string | null): MapColors { + if (theme && theme in MAP_THEMES) { + return MAP_THEMES[theme as ThemeVariant] + } + return MAP_THEMES.light } diff --git a/src/app/env.d.ts b/src/app/env.d.ts index 57835bb125..a5309effaf 100644 --- a/src/app/env.d.ts +++ b/src/app/env.d.ts @@ -16,3 +16,17 @@ declare module "*?raw" { const content: string export default content } + +// We're importing a transitive dependency to avoid a bug. +declare module "next-themes" { + export function ThemeProvider(props: { + attribute: "class" | "data-theme" | "style" + children: React.ReactNode + }): JSX.Element + + export function useTheme(): { + theme: string | undefined + setTheme: (theme: string) => void + resolvedTheme: string + } +} diff --git a/src/components/theme-switch.tsx b/src/components/theme-switch.tsx index 2ee09eacbe..ae62fe8a3d 100644 --- a/src/components/theme-switch.tsx +++ b/src/components/theme-switch.tsx @@ -1,7 +1,6 @@ "use client" import { clsx } from "clsx" -// @ts-expect-error we use a transitive-dependency and this one is vulnerable to context clash import { useTheme } from "next-themes" import { Select } from "@base-ui-components/react/select" import { useMounted } from "nextra/hooks" From 819197b60f27bee8404c9ed6b7ef0988b4a5704a Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 10 Nov 2025 21:21:44 +0100 Subject: [PATCH 032/133] add comments --- src/app/(main)/community/events/meetups-map.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/(main)/community/events/meetups-map.tsx b/src/app/(main)/community/events/meetups-map.tsx index 1b23a33ac7..97728ae33e 100644 --- a/src/app/(main)/community/events/meetups-map.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -24,12 +24,12 @@ const ASPECT_RATIO = 1.65 const MAP_THEMES = { light: { - sea: [0.9804, 0.9882, 0.9569], - land: [0.8627, 0.8706, 0.8275], + sea: [0.9804, 0.9882, 0.9569], // #FAFCF4 + land: [0.8627, 0.8706, 0.8275], // #DCDED3 }, dark: { - sea: [0.0549, 0.0588, 0.0431], - land: [0.1647, 0.1804, 0.1373], + sea: [0.0549, 0.0588, 0.0431], // neu-900 + land: [0.1647, 0.1804, 0.1373], // a shade darker than neu-800 }, } satisfies Record From fca9b16b729fd0ef6859a1c1eeb77fde53d6c938 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 10 Nov 2025 21:28:40 +0100 Subject: [PATCH 033/133] Preserve consistent aspect ratio --- src/app/(main)/community/events/map/engine.ts | 61 +++++++++++-------- .../(main)/community/events/map/shaders.ts | 3 +- .../(main)/community/events/meetups-map.tsx | 13 ++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/app/(main)/community/events/map/engine.ts b/src/app/(main)/community/events/map/engine.ts index 418884af23..36d48d8f25 100644 --- a/src/app/(main)/community/events/map/engine.ts +++ b/src/app/(main)/community/events/map/engine.ts @@ -42,6 +42,7 @@ export type BootOptions = { initialQuality: SamplingQuality initialCellSize: number initialSquareSize: number + aspectRatio: number theme: MapColors onStatsChange?: (stats: MapStats) => void signal?: AbortSignal @@ -122,6 +123,7 @@ class MapEngine implements MapHandle { private quality: SamplingQuality private cellSize: number private squareSize: number + private aspectRatio: number private zoom = 1 private pan = new Float32Array([0, 0]) private target = new Float32Array([0.5, 0.5]) @@ -163,6 +165,7 @@ class MapEngine implements MapHandle { this.dotsProgram = options.dotsProgram this.markersProgram = options.markersProgram this.landTexture = options.landTexture + this.aspectRatio = options.aspectRatio this.quality = options.initialQuality this.cellSize = clamp(options.initialCellSize, MIN_CELL, MAX_CELL) this.squareSize = clamp( @@ -273,6 +276,14 @@ class MapEngine implements MapHandle { this.landColor.set(colors.land) } + private getWorldDimensions() { + const width = this.canvas.width || 1 + const height = this.canvas.height || 1 + const worldHeight = Math.min(width / this.aspectRatio, height) + const worldWidth = worldHeight * this.aspectRatio + return { width, height, worldWidth, worldHeight } + } + resetView() { this.zoom = 1 this.target[0] = 0.5 @@ -335,10 +346,9 @@ class MapEngine implements MapHandle { const scale = getDevicePixelRatio() const dx = (event.clientX - this.pointer.startX) * scale const dy = (event.clientY - this.pointer.startY) * scale - const width = this.canvas.width || 1 - const height = this.canvas.height || 1 - const invWidth = width > 0 ? 1 / (width * this.zoom) : 0 - const invHeight = height > 0 ? 1 / (height * this.zoom) : 0 + const { worldWidth, worldHeight } = this.getWorldDimensions() + const invWidth = worldWidth > 0 ? 1 / (worldWidth * this.zoom) : 0 + const invHeight = worldHeight > 0 ? 1 / (worldHeight * this.zoom) : 0 const prevX = this.target[0] const prevY = this.target[1] const nextX = this.pointer.targetAtStart[0] - dx * invWidth @@ -377,15 +387,21 @@ class MapEngine implements MapHandle { const nextZoom = clamp(previousZoom * zoomFactor, MIN_ZOOM, MAX_ZOOM) if (nextZoom === previousZoom) return - const width = this.canvas.width || 1 - const height = this.canvas.height || 1 - const world = this.screenToWorld(pointer[0], pointer[1], previousZoom) + const { width, height, worldWidth, worldHeight } = this.getWorldDimensions() + + // Calculate world coordinates at pointer position before zoom + const worldX = (pointer[0] - this.pan[0]) / (worldWidth * previousZoom) + const worldY = (pointer[1] - this.pan[1]) / (worldHeight * previousZoom) + + // Update zoom this.zoom = nextZoom + + // Calculate new target so that worldX, worldY stays under the pointer this.target[0] = wrap01( - world[0] + (width * 0.5 - pointer[0]) / (width * nextZoom), + worldX - (pointer[0] - width * 0.5) / (worldWidth * nextZoom), ) this.target[1] = clamp01( - world[1] + (height * 0.5 - pointer[1]) / (height * nextZoom), + worldY - (pointer[1] - height * 0.5) / (worldHeight * nextZoom), ) this.updatePanFromTarget() this.velocity[0] = 0 @@ -441,18 +457,18 @@ class MapEngine implements MapHandle { private render() { const gl = this.gl - const width = this.canvas.width || 1 - const height = this.canvas.height || 1 + const { width, height, worldWidth, worldHeight } = this.getWorldDimensions() gl.viewport(0, 0, width, height) gl.clearColor(this.seaColor[0], this.seaColor[1], this.seaColor[2], 1) gl.clear(gl.COLOR_BUFFER_BIT) - const panX = wrapCentered(this.pan[0], width * this.zoom) + const panX = wrapCentered(this.pan[0], worldWidth * this.zoom) const panY = this.pan[1] gl.useProgram(this.dotsProgram) gl.bindVertexArray(this.fullscreenVAO) setUniform2f(gl, this.dotsProgram, "uRes", width, height) + setUniform2f(gl, this.dotsProgram, "uWorldSize", worldWidth, worldHeight) setUniform2f(gl, this.dotsProgram, "uPan", panX, panY) setUniform1f(gl, this.dotsProgram, "uZoom", this.zoom) setUniform1f(gl, this.dotsProgram, "uCell", this.cellSize) @@ -483,15 +499,14 @@ class MapEngine implements MapHandle { } private updateMarkerCenters(panX: number, panY: number) { - const width = this.canvas.width || 1 - const height = this.canvas.height || 1 + const { width, height, worldWidth, worldHeight } = this.getWorldDimensions() const zoom = this.zoom - const period = width * zoom || width + const period = worldWidth * zoom || worldWidth let cursor = 0 for (let i = 0; i < this.markerInstances.length; i++) { const base = this.markerInstances[i] const baseX = base.uv[0] * period + panX - const baseY = base.uv[1] * height + panY + const baseY = base.uv[1] * worldHeight * this.zoom + panY const wrapped = wrapPositive(baseX, period) cursor = this.writeMarker(cursor, wrapped, baseY, base) if (period < width + base.size) { @@ -567,17 +582,15 @@ class MapEngine implements MapHandle { } private updatePanFromTarget() { - const width = this.canvas.width || 1 - const height = this.canvas.height || 1 - this.pan[0] = width * 0.5 - this.target[0] * width * this.zoom - this.pan[1] = height * 0.5 - this.target[1] * height * this.zoom + const { width, height, worldWidth, worldHeight } = this.getWorldDimensions() + this.pan[0] = width * 0.5 - this.target[0] * worldWidth * this.zoom + this.pan[1] = height * 0.5 - this.target[1] * worldHeight * this.zoom } private screenToWorld(px: number, py: number, zoom = this.zoom) { - const width = this.canvas.width || 1 - const height = this.canvas.height || 1 - const x = width > 0 ? (px - this.pan[0]) / (width * zoom) : 0 - const y = height > 0 ? (py - this.pan[1]) / (height * zoom) : 0 + const { worldWidth, worldHeight } = this.getWorldDimensions() + const x = worldWidth > 0 ? (px - this.pan[0]) / (worldWidth * zoom) : 0 + const y = worldHeight > 0 ? (py - this.pan[1]) / (worldHeight * zoom) : 0 return [x, y] as [number, number] } } diff --git a/src/app/(main)/community/events/map/shaders.ts b/src/app/(main)/community/events/map/shaders.ts index 6fdc77681b..f68dcd129e 100644 --- a/src/app/(main)/community/events/map/shaders.ts +++ b/src/app/(main)/community/events/map/shaders.ts @@ -18,6 +18,7 @@ precision highp float; out vec4 outColor; uniform vec2 uRes; +uniform vec2 uWorldSize; uniform vec2 uPan; uniform float uZoom; uniform float uCell; @@ -55,7 +56,7 @@ void main() { vec2 fragPx = gl_FragCoord.xy; vec2 cell = floor(fragPx / uCell) * uCell; vec2 center = cell + vec2(0.5 * uCell); - vec2 uv = (center / uRes) / uZoom - (uPan / (uRes * uZoom)); + vec2 uv = (center / uWorldSize) / uZoom - (uPan / (uWorldSize * uZoom)); uv.x = fract(uv.x); if (uv.y < 0.0 || uv.y > 1.0) { discard; diff --git a/src/app/(main)/community/events/meetups-map.tsx b/src/app/(main)/community/events/meetups-map.tsx index 97728ae33e..2d01177b0d 100644 --- a/src/app/(main)/community/events/meetups-map.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -20,6 +20,7 @@ const INITIAL_QUALITY: SamplingQuality = 4 const QUALITIES: SamplingQuality[] = [1, 4, 16] const HUB_MEETUP_IDS = new Set(["paris"]) const LAND_MASK_URL = new URL("./map/land-mask.png", import.meta.url).toString() +// 1,4992679356 const ASPECT_RATIO = 1.65 const MAP_THEMES = { @@ -49,7 +50,7 @@ export function MeetupsMap() { const handleRef = useRef() const { resolvedTheme } = useTheme() const themeColors = useMemo( - () => getMapColors(resolvedTheme), + () => MAP_THEMES[resolvedTheme as ThemeVariant] || MAP_THEMES.light, [resolvedTheme], ) const initialThemeRef = useRef(themeColors) @@ -84,6 +85,7 @@ export function MeetupsMap() { initialCellSize: CELL_SIZE, initialSquareSize: SQUARE_SIZE, initialQuality: INITIAL_QUALITY, + aspectRatio: ASPECT_RATIO, theme: initialThemeRef.current, onStatsChange: next => setStats(next), signal: abortController.signal, @@ -135,7 +137,7 @@ export function MeetupsMap() { @@ -195,10 +197,3 @@ export function MeetupsMap() {
) } - -function getMapColors(theme?: string | null): MapColors { - if (theme && theme in MAP_THEMES) { - return MAP_THEMES[theme as ThemeVariant] - } - return MAP_THEMES.light -} From 126178610e99d503208e9197821960decbcdbb65 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 10 Nov 2025 21:36:16 +0100 Subject: [PATCH 034/133] Clean up options --- src/app/(main)/community/events/map/engine.ts | 1 - .../(main)/community/events/meetups-map.tsx | 77 ++++--------------- 2 files changed, 13 insertions(+), 65 deletions(-) diff --git a/src/app/(main)/community/events/map/engine.ts b/src/app/(main)/community/events/map/engine.ts index 36d48d8f25..8329e90db8 100644 --- a/src/app/(main)/community/events/map/engine.ts +++ b/src/app/(main)/community/events/map/engine.ts @@ -142,7 +142,6 @@ class MapEngine implements MapHandle { private readonly instanceCapacity: number private activeInstances = 0 private readonly resizeObserver: ResizeObserver - private readonly onStatsChange?: (stats: MapStats) => void private readonly stats: MapStats private readonly hudThrottleMs = 120 private lastHudTime = 0 diff --git a/src/app/(main)/community/events/meetups-map.tsx b/src/app/(main)/community/events/meetups-map.tsx index 2d01177b0d..0e695c46d7 100644 --- a/src/app/(main)/community/events/meetups-map.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -16,11 +16,9 @@ import { const CELL_SIZE = 16 const SQUARE_SIZE = 12 -const INITIAL_QUALITY: SamplingQuality = 4 -const QUALITIES: SamplingQuality[] = [1, 4, 16] +const INITIAL_QUALITY: SamplingQuality = 16 const HUB_MEETUP_IDS = new Set(["paris"]) const LAND_MASK_URL = new URL("./map/land-mask.png", import.meta.url).toString() -// 1,4992679356 const ASPECT_RATIO = 1.65 const MAP_THEMES = { @@ -54,13 +52,7 @@ export function MeetupsMap() { [resolvedTheme], ) const initialThemeRef = useRef(themeColors) - const [stats, setStats] = useState({ - fps: 0, - zoom: 1, - cellSize: CELL_SIZE, - squareSize: SQUARE_SIZE, - quality: INITIAL_QUALITY, - }) + const [status, setStatus] = useState("loading") const [errorMessage, setErrorMessage] = useState(null) @@ -87,7 +79,6 @@ export function MeetupsMap() { initialQuality: INITIAL_QUALITY, aspectRatio: ASPECT_RATIO, theme: initialThemeRef.current, - onStatsChange: next => setStats(next), signal: abortController.signal, }) if (disposed) { @@ -126,14 +117,17 @@ export function MeetupsMap() { const disabled = status !== "ready" - const updateQuality = (quality: SamplingQuality) => { - if (disabled) return - handleRef.current?.setQuality(quality) - } - return ( -
-
+
c * 255).join(", ")})`, + "--sea-light": `rgb(${MAP_THEMES.light.sea.map(c => c * 255).join(", ")})`, + } as {} + } + > +
{status !== "ready" && ( -
+
{status === "loading" ? "Booting WebGL map…" : `Unable to load the map${errorMessage ? ` (${errorMessage})` : ""}`}
)} - -
-
- FPS - {stats.fps.toFixed(0)} -
-
- Zoom - {stats.zoom.toFixed(2)}× -
-
- - Quality - -
- {QUALITIES.map(quality => ( - - ))} -
-
- -
- -
- Scroll to zoom, drag to pan, press R to reset -
) From 281e7c7fc8e5ad432e5cb85dcd3a016e8062054f Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 10 Nov 2025 21:38:20 +0100 Subject: [PATCH 035/133] wip --- src/app/(main)/community/events/map/engine.ts | 10 +++++++--- src/app/(main)/community/events/meetups-map.tsx | 11 ++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/app/(main)/community/events/map/engine.ts b/src/app/(main)/community/events/map/engine.ts index 8329e90db8..8010e329cd 100644 --- a/src/app/(main)/community/events/map/engine.ts +++ b/src/app/(main)/community/events/map/engine.ts @@ -64,6 +64,10 @@ const HALO_COLOR: [number, number, number, number] = [ 0.7, HUB_HALO_ALPHA, ] +const GOOGLE_MAPS_IDLE_CURSOR = + 'url("https://maps.gstatic.com/mapfiles/openhand_8_8.cur"), default' +const GOOGLE_MAPS_DRAG_CURSOR = + 'url("https://maps.gstatic.com/mapfiles/closedhand_8_8.cur"), move' /** * Per-frame damping factor (scaled by dt / (1/60s)). * Decrease value to increase damping. @@ -305,7 +309,7 @@ class MapEngine implements MapHandle { } private attachEvents() { - this.canvas.style.cursor = "grab" + this.canvas.style.cursor = GOOGLE_MAPS_IDLE_CURSOR this.canvas.addEventListener("pointerdown", this.handlePointerDown) this.canvas.addEventListener("pointermove", this.handlePointerMove) this.canvas.addEventListener("pointerup", this.handlePointerUp) @@ -337,7 +341,7 @@ class MapEngine implements MapHandle { this.velocity[0] = 0 this.velocity[1] = 0 this.canvas.setPointerCapture(event.pointerId) - this.canvas.style.cursor = "grabbing" + this.canvas.style.cursor = GOOGLE_MAPS_DRAG_CURSOR } private handlePointerMove = (event: PointerEvent) => { @@ -369,7 +373,7 @@ class MapEngine implements MapHandle { if (!this.pointer.active || event.pointerId !== this.pointer.id) return this.pointer.active = false this.canvas.releasePointerCapture(event.pointerId) - this.canvas.style.cursor = "grab" + this.canvas.style.cursor = GOOGLE_MAPS_IDLE_CURSOR this.pointer.lastMoveTime = 0 } diff --git a/src/app/(main)/community/events/meetups-map.tsx b/src/app/(main)/community/events/meetups-map.tsx index 0e695c46d7..f810c23ffa 100644 --- a/src/app/(main)/community/events/meetups-map.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -8,7 +8,6 @@ import { meetups } from "@/components/meetups" import { bootMeetupsMap, type MapHandle, - type MapStats, type MarkerPoint, type SamplingQuality, type MapColors, @@ -23,11 +22,11 @@ const ASPECT_RATIO = 1.65 const MAP_THEMES = { light: { - sea: [0.9804, 0.9882, 0.9569], // #FAFCF4 - land: [0.8627, 0.8706, 0.8275], // #DCDED3 + sea: [0.9804, 0.9882, 0.9569], // neu-50 + land: [0.8627, 0.8706, 0.8275], // neu-300 }, dark: { - sea: [0.0549, 0.0588, 0.0431], // neu-900 + sea: [0.0549, 0.0588, 0.0431], // neu-50 land: [0.1647, 0.1804, 0.1373], // a shade darker than neu-800 }, } satisfies Record @@ -115,8 +114,6 @@ export function MeetupsMap() { handleRef.current.setThemeColors(themeColors) }, [themeColors]) - const disabled = status !== "ready" - return (
-
+
Date: Mon, 10 Nov 2025 22:02:34 +0100 Subject: [PATCH 036/133] Limit panning --- src/app/(main)/community/events/map/engine.ts | 39 +++++++++++++++---- .../(main)/community/events/meetups-map.tsx | 10 ++--- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/app/(main)/community/events/map/engine.ts b/src/app/(main)/community/events/map/engine.ts index 8010e329cd..feae642c94 100644 --- a/src/app/(main)/community/events/map/engine.ts +++ b/src/app/(main)/community/events/map/engine.ts @@ -49,7 +49,7 @@ export type BootOptions = { } const MIN_ZOOM = 1 -const MAX_ZOOM = 8 +const MAX_ZOOM = 20 const MIN_CELL = 6 const MAX_CELL = 24 const MIN_SQUARE = 2 @@ -64,6 +64,7 @@ const HALO_COLOR: [number, number, number, number] = [ 0.7, HUB_HALO_ALPHA, ] +const MAX_VERTICAL_TRAVEL_RATIO = 0.35 const GOOGLE_MAPS_IDLE_CURSOR = 'url("https://maps.gstatic.com/mapfiles/openhand_8_8.cur"), default' const GOOGLE_MAPS_DRAG_CURSOR = @@ -287,6 +288,31 @@ class MapEngine implements MapHandle { return { width, height, worldWidth, worldHeight } } + private clampLatitude(value: number) { + const { min, max } = this.getLatitudeBounds() + return clamp(value, min, max) + } + + private getLatitudeBounds() { + const { height, worldHeight } = this.getWorldDimensions() + const zoomedHeight = worldHeight * this.zoom + if (!isFinite(zoomedHeight) || zoomedHeight <= 0) { + return { min: 0.5, max: 0.5 } + } + const fraction = height / (2 * zoomedHeight) + if (fraction >= 0.5) { + return { min: 0.5, max: 0.5 } + } + const margin = clamp01(fraction) + const center = 0.5 + const fullTravel = center - margin + if (fullTravel <= 0) { + return { min: center, max: center } + } + const limitedTravel = fullTravel * MAX_VERTICAL_TRAVEL_RATIO + return { min: center - limitedTravel, max: center + limitedTravel } + } + resetView() { this.zoom = 1 this.target[0] = 0.5 @@ -357,7 +383,7 @@ class MapEngine implements MapHandle { const nextX = this.pointer.targetAtStart[0] - dx * invWidth const nextY = this.pointer.targetAtStart[1] + dy * invHeight this.target[0] = wrap01(nextX) - this.target[1] = clamp01(nextY) + this.target[1] = this.clampLatitude(nextY) this.updatePanFromTarget() const now = performance.now() @@ -403,7 +429,7 @@ class MapEngine implements MapHandle { this.target[0] = wrap01( worldX - (pointer[0] - width * 0.5) / (worldWidth * nextZoom), ) - this.target[1] = clamp01( + this.target[1] = this.clampLatitude( worldY - (pointer[1] - height * 0.5) / (worldHeight * nextZoom), ) this.updatePanFromTarget() @@ -429,8 +455,6 @@ class MapEngine implements MapHandle { const prevHeight = this.canvas.height || height this.canvas.width = width this.canvas.height = height - const scaleX = prevWidth ? width / prevWidth : 1 - const scaleY = prevHeight ? height / prevHeight : 1 this.updatePanFromTarget() this.gl.viewport(0, 0, width, height) } @@ -571,8 +595,9 @@ class MapEngine implements MapHandle { } const dt = Math.max(dtMs, 0) this.target[0] = wrap01(this.target[0] + velX * dt) - const nextY = clamp01(this.target[1] + velY * dt) - if (nextY === 0 || nextY === 1) { + const { min, max } = this.getLatitudeBounds() + const nextY = clamp(this.target[1] + velY * dt, min, max) + if (nextY === min || nextY === max) { this.velocity[1] = 0 } this.target[1] = nextY diff --git a/src/app/(main)/community/events/meetups-map.tsx b/src/app/(main)/community/events/meetups-map.tsx index f810c23ffa..202aeb9473 100644 --- a/src/app/(main)/community/events/meetups-map.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -15,7 +15,7 @@ import { const CELL_SIZE = 16 const SQUARE_SIZE = 12 -const INITIAL_QUALITY: SamplingQuality = 16 +const INITIAL_QUALITY: SamplingQuality = 4 const HUB_MEETUP_IDS = new Set(["paris"]) const LAND_MASK_URL = new URL("./map/land-mask.png", import.meta.url).toString() const ASPECT_RATIO = 1.65 @@ -119,12 +119,12 @@ export function MeetupsMap() { className="my-6" style={ { - "--sea-dark": `rgb(${MAP_THEMES.dark.sea.map(c => c * 255).join(", ")})`, - "--sea-light": `rgb(${MAP_THEMES.light.sea.map(c => c * 255).join(", ")})`, - } as {} + "--sea-dark": `rgb(${MAP_THEMES.dark.sea.map(c => Math.round(c * 255)).join(", ")})`, + "--sea-light": `rgb(${MAP_THEMES.light.sea.map(c => Math.round(c * 255)).join(", ")})`, + } as React.CSSProperties } > -
+
Date: Mon, 10 Nov 2025 22:03:47 +0100 Subject: [PATCH 037/133] Make the pinch zoom stronger --- src/app/(main)/community/events/map/engine.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/(main)/community/events/map/engine.ts b/src/app/(main)/community/events/map/engine.ts index feae642c94..1aa720c202 100644 --- a/src/app/(main)/community/events/map/engine.ts +++ b/src/app/(main)/community/events/map/engine.ts @@ -411,7 +411,8 @@ class MapEngine implements MapHandle { (event.clientX - rect.left) * scale, (event.clientY - rect.top) * scale, ] - const zoomFactor = Math.exp(-event.deltaY * 0.0015) + const wheelSensitivity = event.ctrlKey ? 0.005 : 0.0015 + const zoomFactor = Math.exp(-event.deltaY * wheelSensitivity) const previousZoom = this.zoom const nextZoom = clamp(previousZoom * zoomFactor, MIN_ZOOM, MAX_ZOOM) if (nextZoom === previousZoom) return From 15a5473d24d1d4abce281b8d779ccdd5d7995a43 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 10 Nov 2025 22:34:07 +0100 Subject: [PATCH 038/133] Move the styles inline to the file --- src/app/(main)/layout.tsx | 1 + src/components/sidebar/index.tsx | 61 ++++++++++++++++---------------- tailwind.config.ts | 7 ++++ 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 80638396ef..b4996d34c0 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -26,6 +26,7 @@ export default function MainLayout({ toc={[]} docsDirectories={docsDirectories} fullDirectories={directories} + asPopover />
{children} diff --git a/src/components/sidebar/index.tsx b/src/components/sidebar/index.tsx index 59caad64e1..4dca2ccf03 100644 --- a/src/components/sidebar/index.tsx +++ b/src/components/sidebar/index.tsx @@ -52,8 +52,8 @@ const Folder = memo(function FolderInner(props: FolderProps) { const classes = { link: cn( - "_flex _px-2 _py-1.5 _text-sm _transition-colors [word-break:break-word]", - "_cursor-pointer contrast-more:border contrast-more:hover:underline gql-focus-visible focus-visible:outline-offset-1", + "flex px-2 py-1.5 text-sm transition-colors [word-break:break-word]", + "cursor-pointer contrast-more:border contrast-more:hover:underline gql-focus-visible focus-visible:outline-offset-1", ), inactive: cn( "text-neu-800 hover:bg-neu-100 hover:text-neu-900 hover:bg-neu-100 dark:hover:bg-neu-50/50", @@ -61,13 +61,13 @@ const classes = { ), active: cn( "bg-pri-lighter/25 text-pri-dark dark:bg-pri-light/10 dark:text-pri-light", - "contrast-more:_border-primary-500 contrast-more:dark:_border-primary-500", + "contrast-more:border-primary-500 contrast-more:dark:border-primary-500", ), - list: cn("_flex _flex-col _gap-1"), + list: cn("flex flex-col gap-1"), border: cn( - "_relative before:_absolute before:_inset-y-1", - 'before:_w-px before:bg-neu-100 before:_content-[""] dark:before:bg-neu-50', - "ltr:_pl-3 ltr:before:_left-0 rtl:_pr-3 rtl:before:_right-0", + "relative before:absolute before:inset-y-1", + 'before:w-px before:bg-neu-100 before:content-[""] dark:before:bg-neu-50', + "pl-3 before:left-0", ), } @@ -164,8 +164,8 @@ function FolderImpl({ item, anchors, onFocus }: FolderProps): ReactElement { } data-href={isLink ? undefined : item.route} className={cn( - "_items-center _justify-between _gap-2", - !isLink && "_text-left _w-full", + "items-center justify-between gap-2", + !isLink && "w-full text-left", classes.link, active ? classes.active : classes.inactive, )} @@ -197,17 +197,16 @@ function FolderImpl({ item, anchors, onFocus }: FolderProps): ReactElement { {Array.isArray(item.children) && ( {active && anchors.length > 0 && ( -
+

Event gallery

+

+ A look back at what’s been happening. +

+ +
From ee36721c171e4dc0e5bb8b9949904f66df89029e Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 13 Nov 2025 18:02:47 +0100 Subject: [PATCH 096/133] Format --- src/nextra-theme-docs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nextra-theme-docs.css b/src/nextra-theme-docs.css index 045fc0d30e..9b3d03e932 100644 --- a/src/nextra-theme-docs.css +++ b/src/nextra-theme-docs.css @@ -2963,5 +2963,5 @@ kbd._border._gap-1 { --nextra-bg: 251, 251, 249; } .dark { - --nextra-bg: 15, 15, 12; + --nextra-bg: 15, 15, 12; } From 34c601764f374c9000e9efa134f7ec2521c58255 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 13 Nov 2025 18:05:03 +0100 Subject: [PATCH 097/133] Remove redundant dependencies --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 485a81ece1..4cb53f9787 100644 --- a/package.json +++ b/package.json @@ -36,14 +36,12 @@ "@headlessui/react": "^2.2.4", "@igorkowalczyk/is-browser": "^5.1.0", "@lezer/highlight": "^1.2.1", - "@maptiler/leaflet-maptilersdk": "^4.1.1", "@next/bundle-analyzer": "^15.4.5", "@plaiceholder/next": "^3.0.0", "@sparticuz/chromium": "^138.0.2", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/nesting": "0.0.0-insiders.565cd3e", "@tailwindcss/typography": "^0.5.15", - "@types/leaflet": "^1.9.21", "autoprefixer": "^10.4.20", "calendar-link": "^2.10.0", "clsx": "^2.1.1", From c472e5331ab0067f4143b6e5106b0e46b182fcff Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Thu, 13 Nov 2025 18:12:22 +0100 Subject: [PATCH 098/133] Update lockfile --- pnpm-lock.yaml | 252 ------------------------------------------------- 1 file changed, 252 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85d440944d..e6234b89f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,9 +65,6 @@ importers: '@lezer/highlight': specifier: 1.2.1 version: 1.2.1 - '@maptiler/leaflet-maptilersdk': - specifier: ^4.1.1 - version: 4.1.1 '@next/bundle-analyzer': specifier: ^15.4.5 version: 15.5.6 @@ -86,9 +83,6 @@ importers: '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.19(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1)) - '@types/leaflet': - specifier: ^1.9.21 - version: 1.9.21 autoprefixer: specifier: ^10.4.20 version: 10.4.21(postcss@8.5.6) @@ -1850,46 +1844,6 @@ packages: '@lezer/lr@1.4.2': resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} - '@mapbox/geojson-rewind@0.5.2': - resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==} - hasBin: true - - '@mapbox/jsonlint-lines-primitives@2.0.2': - resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==} - engines: {node: '>= 0.6'} - - '@mapbox/point-geometry@1.1.0': - resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==} - - '@mapbox/tiny-sdf@2.0.7': - resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==} - - '@mapbox/unitbezier@0.0.1': - resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} - - '@mapbox/vector-tile@2.0.4': - resolution: {integrity: sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==} - - '@mapbox/whoots-js@3.1.0': - resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} - engines: {node: '>=6.0.0'} - - '@maplibre/maplibre-gl-style-spec@23.3.0': - resolution: {integrity: sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==} - hasBin: true - - '@maplibre/vt-pbf@4.0.3': - resolution: {integrity: sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==} - - '@maptiler/client@2.5.1': - resolution: {integrity: sha512-2rveqohOu3xv16EX65rFUQF8fpPppefrGkvUEtZOx50EDHFxrhEVjKM0p95TuT0RLygxF/sw4vAqUFCBOnPbiw==} - - '@maptiler/leaflet-maptilersdk@4.1.1': - resolution: {integrity: sha512-vCE0K9mlVRyAJkWYOngiA1ZEI42eoWvZd21HLYZE/IU1T+OQc4K1bPoap/z0myAXDLxIJ4wlz+vchr5tKgo0uA==} - - '@maptiler/sdk@3.8.0': - resolution: {integrity: sha512-oXLlSyJhKADB1KTfxj5bhnRml3nrvBerhuuo1v+rDJKrUHqQpVPj6PS+z809D/+UB9ZwzzHOBeFZP3vYCK3U5g==} - '@mdx-js/mdx@3.1.0': resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} @@ -2448,9 +2402,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/geojson-vt@3.2.5': - resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} - '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -2466,9 +2417,6 @@ packages: '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} - '@types/leaflet@1.9.21': - resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} - '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} @@ -2505,9 +2453,6 @@ packages: '@types/string-similarity@4.0.2': resolution: {integrity: sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==} - '@types/supercluster@7.1.3': - resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} - '@types/supports-color@8.1.3': resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} @@ -3423,9 +3368,6 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - earcut@3.0.2: - resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3649,10 +3591,6 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3805,9 +3743,6 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - geojson-vt@4.0.2: - resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -3824,10 +3759,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -3842,9 +3773,6 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - gl-matrix@3.4.4: - resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4398,9 +4326,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - js-base64@3.7.8: - resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4447,9 +4372,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-pretty-compact@4.0.0: - resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} - json-to-pretty-yaml@1.2.2: resolution: {integrity: sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==} engines: {node: '>= 0.2.0'} @@ -4467,9 +4389,6 @@ packages: resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} hasBin: true - kdbush@4.0.2: - resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -4622,10 +4541,6 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} - maplibre-gl@5.6.2: - resolution: {integrity: sha512-SEqYThhUCFf6Lm0TckpgpKnto5u4JsdPYdFJb6g12VtuaFsm3nYdBO+fOmnUYddc8dXihgoGnuXvPPooUcRv5w==} - engines: {node: '>=16.14.0', npm: '>=8.1.0'} - markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -4931,9 +4846,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - murmurhash-js@1.0.0: - resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} - mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -5272,10 +5184,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pbf@4.0.1: - resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} - hasBin: true - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -5393,9 +5301,6 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - potpack@2.1.0: - resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -5504,9 +5409,6 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - protocol-buffers-schema@3.6.0: - resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} - prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -5523,13 +5425,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@7.3.0: - resolution: {integrity: sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==} - engines: {node: '>=18'} - - quickselect@3.0.0: - resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} - ranges-apply@7.0.31: resolution: {integrity: sha512-J/METHTxhQTRpLS3hzkvipyRyheAYmAa6BeZaJaTTutIU4spGfU8vKBnhSgKa+WAVAqpZKzqcX29+HHR2lcKLg==} engines: {node: '>=14.18.0'} @@ -5733,9 +5628,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve-protobuf-schema@2.1.0: - resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -6115,9 +6007,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supercluster@8.0.1: - resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -6196,9 +6085,6 @@ packages: tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} - tinyqueue@3.0.0: - resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} - title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} @@ -8546,67 +8432,6 @@ snapshots: dependencies: '@lezer/common': 1.2.3 - '@mapbox/geojson-rewind@0.5.2': - dependencies: - get-stream: 6.0.1 - minimist: 1.2.8 - - '@mapbox/jsonlint-lines-primitives@2.0.2': {} - - '@mapbox/point-geometry@1.1.0': {} - - '@mapbox/tiny-sdf@2.0.7': {} - - '@mapbox/unitbezier@0.0.1': {} - - '@mapbox/vector-tile@2.0.4': - dependencies: - '@mapbox/point-geometry': 1.1.0 - '@types/geojson': 7946.0.16 - pbf: 4.0.1 - - '@mapbox/whoots-js@3.1.0': {} - - '@maplibre/maplibre-gl-style-spec@23.3.0': - dependencies: - '@mapbox/jsonlint-lines-primitives': 2.0.2 - '@mapbox/unitbezier': 0.0.1 - json-stringify-pretty-compact: 4.0.0 - minimist: 1.2.8 - quickselect: 3.0.0 - rw: 1.3.3 - tinyqueue: 3.0.0 - - '@maplibre/vt-pbf@4.0.3': - dependencies: - '@mapbox/point-geometry': 1.1.0 - '@mapbox/vector-tile': 2.0.4 - '@types/geojson-vt': 3.2.5 - '@types/supercluster': 7.1.3 - geojson-vt: 4.0.2 - pbf: 4.0.1 - supercluster: 8.0.1 - - '@maptiler/client@2.5.1': - dependencies: - quick-lru: 7.3.0 - - '@maptiler/leaflet-maptilersdk@4.1.1': - dependencies: - '@maptiler/sdk': 3.8.0 - '@types/leaflet': 1.9.21 - leaflet: 1.9.4 - - '@maptiler/sdk@3.8.0': - dependencies: - '@maplibre/maplibre-gl-style-spec': 23.3.0 - '@maptiler/client': 2.5.1 - events: 3.3.0 - gl-matrix: 3.4.4 - js-base64: 3.7.8 - maplibre-gl: 5.6.2 - uuid: 11.1.0 - '@mdx-js/mdx@3.1.0(acorn@8.15.0)': dependencies: '@types/estree': 1.0.8 @@ -9244,10 +9069,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/geojson-vt@3.2.5': - dependencies: - '@types/geojson': 7946.0.16 - '@types/geojson@7946.0.16': {} '@types/hast@3.0.4': @@ -9264,10 +9085,6 @@ snapshots: '@types/katex@0.16.7': {} - '@types/leaflet@1.9.21': - dependencies: - '@types/geojson': 7946.0.16 - '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.17.20 @@ -9305,10 +9122,6 @@ snapshots: '@types/string-similarity@4.0.2': {} - '@types/supercluster@7.1.3': - dependencies: - '@types/geojson': 7946.0.16 - '@types/supports-color@8.1.3': {} '@types/tern@0.23.9': @@ -10309,8 +10122,6 @@ snapshots: duplexer@0.1.2: {} - earcut@3.0.2: {} - eastasianwidth@0.2.0: {} electron-to-chromium@1.5.207: {} @@ -10691,8 +10502,6 @@ snapshots: eventemitter3@5.0.1: {} - events@3.3.0: {} - execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -10849,8 +10658,6 @@ snapshots: gensync@1.0.0-beta.2: {} - geojson-vt@4.0.2: {} - get-caller-file@2.0.5: {} get-east-asian-width@1.4.0: {} @@ -10873,8 +10680,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@6.0.1: {} - get-stream@8.0.1: {} get-symbol-description@1.1.0: @@ -10889,8 +10694,6 @@ snapshots: github-slugger@2.0.0: {} - gl-matrix@3.4.4: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -11517,8 +11320,6 @@ snapshots: jiti@2.6.1: {} - js-base64@3.7.8: {} - js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -11571,8 +11372,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-pretty-compact@4.0.0: {} - json-to-pretty-yaml@1.2.2: dependencies: remedial: 1.0.8 @@ -11591,8 +11390,6 @@ snapshots: dependencies: commander: 8.3.0 - kdbush@4.0.2: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -11743,31 +11540,6 @@ snapshots: map-cache@0.2.2: {} - maplibre-gl@5.6.2: - dependencies: - '@mapbox/geojson-rewind': 0.5.2 - '@mapbox/jsonlint-lines-primitives': 2.0.2 - '@mapbox/point-geometry': 1.1.0 - '@mapbox/tiny-sdf': 2.0.7 - '@mapbox/unitbezier': 0.0.1 - '@mapbox/vector-tile': 2.0.4 - '@mapbox/whoots-js': 3.1.0 - '@maplibre/maplibre-gl-style-spec': 23.3.0 - '@maplibre/vt-pbf': 4.0.3 - '@types/geojson': 7946.0.16 - '@types/geojson-vt': 3.2.5 - '@types/supercluster': 7.1.3 - earcut: 3.0.2 - geojson-vt: 4.0.2 - gl-matrix: 3.4.4 - kdbush: 4.0.2 - murmurhash-js: 1.0.0 - pbf: 4.0.1 - potpack: 2.1.0 - quickselect: 3.0.0 - supercluster: 8.0.1 - tinyqueue: 3.0.0 - markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -12364,8 +12136,6 @@ snapshots: ms@2.1.3: {} - murmurhash-js@1.0.0: {} - mute-stream@2.0.0: {} mz@2.7.0: @@ -12781,10 +12551,6 @@ snapshots: pathe@2.0.3: {} - pbf@4.0.1: - dependencies: - resolve-protobuf-schema: 2.1.0 - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -12891,8 +12657,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - potpack@2.1.0: {} - prelude-ls@1.2.1: {} prettier-plugin-pkg@0.21.2(prettier@3.5.3): @@ -12934,8 +12698,6 @@ snapshots: property-information@7.1.0: {} - protocol-buffers-schema@3.6.0: {} - prr@1.0.1: optional: true @@ -12950,10 +12712,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@7.3.0: {} - - quickselect@3.0.0: {} - ranges-apply@7.0.31: dependencies: ranges-merge: 9.0.30 @@ -13269,10 +13027,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve-protobuf-schema@2.1.0: - dependencies: - protocol-buffers-schema: 3.6.0 - resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -13746,10 +13500,6 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 - supercluster@8.0.1: - dependencies: - kdbush: 4.0.2 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -13856,8 +13606,6 @@ snapshots: tinyexec@1.0.1: {} - tinyqueue@3.0.0: {} - title-case@3.0.3: dependencies: tslib: 2.8.1 From 548b2f50eee51b87340215d9e50c5108ba8ad3a6 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 12:11:34 +0100 Subject: [PATCH 099/133] Reduce one level of nesting in e2e test --- test/e2e/graphql-interactive.spec.ts | 248 +++++++++++++-------------- 1 file changed, 123 insertions(+), 125 deletions(-) diff --git a/test/e2e/graphql-interactive.spec.ts b/test/e2e/graphql-interactive.spec.ts index 21fd493df2..fe6c26a16b 100644 --- a/test/e2e/graphql-interactive.spec.ts +++ b/test/e2e/graphql-interactive.spec.ts @@ -61,154 +61,152 @@ async function expectJsonResult( .toStrictEqual(expectedResult) } -test.describe("interactive examples", () => { - test.describe("Learn", () => { - test("adds appearsIn field to hero query and gets correct response", async ({ - page, - }) => { - await page.goto("/learn") - await page.waitForSelector(".cm-editor", { timeout: 10000 }) +test.describe("Learn", () => { + test("adds appearsIn field to hero query and gets correct response", async ({ + page, + }) => { + await page.goto("/learn") + await page.waitForSelector(".cm-editor", { timeout: 10000 }) - const heroEditor = await findEditorByContent(page, "hero") + const heroEditor = await findEditorByContent(page, "hero") - await typeInQuery(page, heroEditor, "name", "ap") - await page.keyboard.press("Control+Space") + await typeInQuery(page, heroEditor, "name", "ap") + await page.keyboard.press("Control+Space") - const autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") - await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) + const autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") + await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) - const appearsInSuggestion = page - .locator(".cm-completionLabel") - .filter({ hasText: "appearsIn" }) + const appearsInSuggestion = page + .locator(".cm-completionLabel") + .filter({ hasText: "appearsIn" }) - expect(page.locator(".cm-completionDetail").first()).toHaveText( - "[Episode]!", - ) + expect(page.locator(".cm-completionDetail").first()).toHaveText( + "[Episode]!", + ) - if (await appearsInSuggestion.isVisible()) { - await appearsInSuggestion.click() - } else { - await page.keyboard.press("Enter") - } + if (await appearsInSuggestion.isVisible()) { + await appearsInSuggestion.click() + } else { + await page.keyboard.press("Enter") + } + + const resultViewer = page.locator(".result-window").first() - const resultViewer = page.locator(".result-window").first() + await expectJsonResult(resultViewer, { + data: { + hero: { + name: "R2-D2", + appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], + }, + }, + }) + }) + + test("edits variables and receives an expected mutation result", async ({ + page, + }) => { + await page.goto("/learn/mutations") + await page.waitForLoadState("networkidle") + + const mutationEditor = await findEditorByContent( + page, + "CreateReviewForEpisode", + ) + + const variableEditor = mutationEditor.locator(".variable-editor").first() + + if (await variableEditor.isVisible()) { + await variableEditor.click() + + await page.getByText('"This is a great movie!"').first().click() + await page.keyboard.press("ControlOrMeta+ArrowRight") + for (let i = 0; i < 4; i++) + await page.keyboard.press("Alt+Shift+ArrowLeft") + await page.keyboard.type('almost as good as Andor"') + + const resultViewer = mutationEditor.locator(".result-window") await expectJsonResult(resultViewer, { data: { - hero: { - name: "R2-D2", - appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], + createReview: { + stars: 5, + commentary: "This is almost as good as Andor", }, }, }) - }) + } + }) +}) - test("edits variables and receives an expected mutation result", async ({ - page, - }) => { - await page.goto("/learn/mutations") - await page.waitForLoadState("networkidle") - - const mutationEditor = await findEditorByContent( - page, - "CreateReviewForEpisode", - ) - - const variableEditor = mutationEditor.locator(".variable-editor").first() - - if (await variableEditor.isVisible()) { - await variableEditor.click() - - await page.getByText('"This is a great movie!"').first().click() - await page.keyboard.press("ControlOrMeta+ArrowRight") - for (let i = 0; i < 4; i++) - await page.keyboard.press("Alt+Shift+ArrowLeft") - await page.keyboard.type('almost as good as Andor"') - - const resultViewer = mutationEditor.locator(".result-window") - - await expectJsonResult(resultViewer, { - data: { - createReview: { - stars: 5, - commentary: "This is almost as good as Andor", - }, - }, - }) - } - }) +test.describe("Landing", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/") + await page.waitForLoadState("networkidle") + page.locator(`text="How it works"`).scrollIntoViewIfNeeded() }) - test.describe("Landing", () => { - test.beforeEach(async ({ page }) => { - await page.goto("/") - await page.waitForLoadState("networkidle") - page.locator(`text="How it works"`).scrollIntoViewIfNeeded() - }) + test("allows editing query and gets updated results", async ({ page }) => { + await page.waitForSelector(".cm-editor", { timeout: 10000 }) + + const editor = page.locator(".cm-editor").first() + + await editor.click() + + await typeInQuery(page, editor, "tagline", "contr") + + await page.keyboard.press("Control+Space") + let autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") + await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) + await page.locator(".cm-completionLabel").click() - test("allows editing query and gets updated results", async ({ page }) => { - await page.waitForSelector(".cm-editor", { timeout: 10000 }) - - const editor = page.locator(".cm-editor").first() - - await editor.click() - - await typeInQuery(page, editor, "tagline", "contr") - - await page.keyboard.press("Control+Space") - let autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") - await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) - await page.locator(".cm-completionLabel").click() - - await page.keyboard.type("(first: 2) {\n") - await page.keyboard.type("cont") - - await page.keyboard.press("Control+Space") - autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") - await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) - await page.locator(".cm-completionLabel").click() - - const resultViewer = page.locator(".result-window").first() - await expect(resultViewer).toBeVisible() - - await expect - .poll(async () => { - const resultContent = await resultViewer.textContent() - const jsonMatch = resultContent?.match(/\{[\s\S]*\}/) - if (jsonMatch) { - try { - const data = JSON.parse(jsonMatch[0]) - if (data?.project?.contributors?.length === 2) { - const contributors = data.project.contributors - return ( - contributors[0].contributions >= contributors[1].contributions - ) - } - return false - } catch { - return false + await page.keyboard.type("(first: 2) {\n") + await page.keyboard.type("cont") + + await page.keyboard.press("Control+Space") + autoCompleteMenu = page.locator(".cm-tooltip-autocomplete") + await expect(autoCompleteMenu).toBeVisible({ timeout: 5000 }) + await page.locator(".cm-completionLabel").click() + + const resultViewer = page.locator(".result-window").first() + await expect(resultViewer).toBeVisible() + + await expect + .poll(async () => { + const resultContent = await resultViewer.textContent() + const jsonMatch = resultContent?.match(/\{[\s\S]*\}/) + if (jsonMatch) { + try { + const data = JSON.parse(jsonMatch[0]) + if (data?.project?.contributors?.length === 2) { + const contributors = data.project.contributors + return ( + contributors[0].contributions >= contributors[1].contributions + ) } + return false + } catch { + return false } - return false - }) - .toBe(true) - }) + } + return false + }) + .toBe(true) + }) - test("shows syntax errors", async ({ page }) => { - await page.waitForSelector(".cm-editor", { timeout: 10000 }) + test("shows syntax errors", async ({ page }) => { + await page.waitForSelector(".cm-editor", { timeout: 10000 }) - const editor = page.locator(".cm-editor").first() + const editor = page.locator(".cm-editor").first() - await editor.click() - await page.keyboard.press("ControlOrMeta+a") - await page.keyboard.press("Backspace") + await editor.click() + await page.keyboard.press("ControlOrMeta+a") + await page.keyboard.press("Backspace") - const playButton = page.getByText("Run query") - await playButton.click() + const playButton = page.getByText("Run query") + await playButton.click() - const resultViewer = page.locator(".result-window").first() - const resultContent = await resultViewer.textContent() - expect(resultContent).toContain("Syntax Error: Unexpected .") - }) + const resultViewer = page.locator(".result-window").first() + const resultContent = await resultViewer.textContent() + expect(resultContent).toContain("Syntax Error: Unexpected .") }) }) From 4e6236756ef664b623f689f898d9a7736941a812 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 12:11:41 +0100 Subject: [PATCH 100/133] Use list reporter --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index de0ed7f7d4..346e5207b8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, - reporter: "html", + reporter: process.env.CI ? "github" : "list", use: { baseURL: "http://localhost:3000", trace: "on-first-retry", From 8a1232e78d2f4f1834416ed70dcf2ff55bf4d506 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 12:14:03 +0100 Subject: [PATCH 101/133] Remove a TODO file --- src/app/(main)/community/events/map/TODO.md | 23 --------------------- 1 file changed, 23 deletions(-) delete mode 100644 src/app/(main)/community/events/map/TODO.md diff --git a/src/app/(main)/community/events/map/TODO.md b/src/app/(main)/community/events/map/TODO.md deleted file mode 100644 index f3861eb99b..0000000000 --- a/src/app/(main)/community/events/map/TODO.md +++ /dev/null @@ -1,23 +0,0 @@ -- [ ] We need some tests, but they're gonna be hard to write. Maybe invariants available only in `process.env.NODE_ENV === "development"`. - - What invariants would those be? -- [ ] Panning should not recompute squares. - - [ ] But zoom works perfectly currently. It's hard to fix panning while making the zoom glitchy & flickery. -- [ ] Markers still sometimes dissapear when zooming. We should ensure we render all of them at all zoom levels. -- [ ] Zooming no sometimes dissapears the squares and then pops them up again what results in flickering. I'd expect that if we zoom we only gain squares and if we zoom out we only lose squares. -- [ ] Devtool lat/lon conversion is still off: clicking London reports ~50°N and clicking Paris reports ~52°N even though the markers render in the correct spots. Need to align `screenToUV`/`handleDebugClick` with `lonLatToUV` so clicks match the displayed locations. - ---- - -# Plan - -Build a pure viewport/tile controller (state + helpers) extracted from src/app/(main)/community/events/map/engine.ts (lines 247-420) so pan/zoom math is testable and shared by pointer + wheel handlers. - -Precompute a zoom-agnostic marker atlas from the existing markerPoints in engine.ts (lines 137-166); store UV bins + marker types so shaders stop looping through all markers per fragment. - -Update dotsFrag (src/app/(main)/community/events/map/shaders.ts (lines 41-112)) to sample the atlas (texture/SSBO/uniform buffer) instead of iterating markers, keeping the on-screen cell grid (CELL_SIZE/SQUARE_SIZE) exactly as-is. - -Add a dev-only diagnostics hook (inside engine.ts (lines 423-487)) that logs marker counts, tile coverage, and invariant failures (e.g., zoomed-out frame must report 19 markers visible). - -Once the atlas + diagnostics work, clean up the TODO list and document how to tweak the atlas resolution vs. visual cell size. - -If you’re good with that list, we can start with the viewport/tile controller extraction and walk through it together. From ed84b5c8f86089fa6766e02c31f7115d3a6bac31 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 12:14:50 +0100 Subject: [PATCH 102/133] getByRole first --- test/e2e/community-events.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index 45d53dc9c1..b92a946ec7 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -28,7 +28,7 @@ test("map loads and Zurich meetup link works", async ({ page }) => { // Find the scrollview container with past events and meetups // Find the Zurich meetup card in the scrollable list (not the map popup) - const link = page.getByText(/Zurich/i).first() + const link = page.getByRole("link", { name: /Zurich/i }).first() await link.scrollIntoViewIfNeeded() await link.click() From b511a15c7b5c3e31a4c67ab5bcf14f00973eafb2 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 12:23:44 +0100 Subject: [PATCH 103/133] Retain screenshots on failure --- playwright.config.ts | 3 +- test/e2e/community-events.spec.ts | 169 ++++++++++++++++-------------- 2 files changed, 91 insertions(+), 81 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 346e5207b8..b594e43fe4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -13,7 +13,8 @@ export default defineConfig({ reporter: process.env.CI ? "github" : "list", use: { baseURL: "http://localhost:3000", - trace: "on-first-retry", + trace: "retain-on-first-failure", + screenshot: "only-on-failure", }, projects: [ diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index b92a946ec7..f92017de26 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -41,84 +41,93 @@ test("map loads and Zurich meetup link works", async ({ page }) => { expect(newPage.url()).toContain("meetup.com/graphql-zurich") }) -test("map tooltip appears on marker hover", async ({ page }) => { - await page.goto("/community/events") - const mapCanvas = page.locator("canvas").first() - await expect(mapCanvas).toBeVisible({ timeout: 10000 }) - await expect - .poll(async () => { - const box = await mapCanvas.boundingBox() - return Boolean(box && box.width > 100 && box.height > 100) +test( + "map tooltip appears on marker hover", + async ({ page }) => { + await page.goto("/community/events") + const mapCanvas = page.locator("canvas").first() + await expect(mapCanvas).toBeVisible({ timeout: 10000 }) + await expect + .poll(async () => { + const box = await mapCanvas.boundingBox() + return Boolean(box && box.width > 100 && box.height > 100) + }) + .toBe(true) + const tooltip = page.getByRole("tooltip") + await expect(tooltip).toHaveCount(0) + await mapCanvas.hover() + const { clientX, clientY } = await page.evaluate(() => { + const canvas = document.querySelector( + "canvas", + ) as HTMLCanvasElement | null + if (!canvas) throw new Error("Canvas not found") + const targetLat = 51.51 + const targetLon = -0.12 + const aspectRatio = 1.65 + const cellSize = 8 + const mercatorLimit = 85.05112878 + const minDisplayedLatitude = -60 + const baseLatitudeOffset = 4 + const baseLongitudeOffset = 0.1 + const clamp01 = (value: number) => { + if (value <= 0) return 0 + if (value >= 1) return 1 + return value + } + const normalizeLongitude = (value: number) => { + let lon = value + while (lon <= -180) lon += 360 + while (lon > 180) lon -= 360 + return lon + } + const latToRawV = (lat: number) => { + const clampedLat = Math.max( + -mercatorLimit, + Math.min(mercatorLimit, lat), + ) + const rad = (clampedLat * Math.PI) / 180 + return ( + 0.5 - Math.log(Math.tan(Math.PI * 0.25 + rad * 0.5)) / (2 * Math.PI) + ) + } + const maxProjectedV = latToRawV(mercatorLimit) + const minProjectedV = latToRawV(minDisplayedLatitude) + const lonLatToUV = (lon: number, lat: number) => { + const adjustedLon = normalizeLongitude(lon + baseLongitudeOffset) + const u = (adjustedLon + 180) / 360 + const adjustedLat = Math.max( + minDisplayedLatitude, + Math.min(mercatorLimit, lat + baseLatitudeOffset), + ) + const rawV = latToRawV(adjustedLat) + const normalizedV = clamp01( + (rawV - maxProjectedV) / (minProjectedV - maxProjectedV), + ) + return [u, normalizedV] as const + } + const { width, height } = canvas + const pixelRatio = window.devicePixelRatio || 1 + const worldHeight = Math.min(width / aspectRatio, height) + const worldWidth = worldHeight * aspectRatio + const panX = width * 0.5 - worldWidth * 0.5 + const panY = height * 0.5 - worldHeight * 0.5 + const [u, v] = lonLatToUV(targetLon, targetLat) + const markerY = 1 - v + const screenX = panX + u * worldWidth + const screenY = panY + markerY * worldHeight + const deviceCell = cellSize * pixelRatio + const cellX = Math.floor(screenX / deviceCell) + const cellY = Math.floor(screenY / deviceCell) + const centerX = (cellX + 0.5) * deviceCell + const centerY = (cellY + 0.5) * deviceCell + const rect = canvas.getBoundingClientRect() + const clientX = rect.left + centerX / pixelRatio + const clientY = rect.bottom - centerY / pixelRatio + return { clientX, clientY } }) - .toBe(true) - const tooltip = page.getByRole("tooltip") - await expect(tooltip).toHaveCount(0) - await mapCanvas.hover() - const { clientX, clientY } = await page.evaluate(() => { - const canvas = document.querySelector("canvas") as HTMLCanvasElement | null - if (!canvas) throw new Error("Canvas not found") - const targetLat = 51.51 - const targetLon = -0.12 - const aspectRatio = 1.65 - const cellSize = 8 - const mercatorLimit = 85.05112878 - const minDisplayedLatitude = -60 - const baseLatitudeOffset = 4 - const baseLongitudeOffset = 0.1 - const clamp01 = (value: number) => { - if (value <= 0) return 0 - if (value >= 1) return 1 - return value - } - const normalizeLongitude = (value: number) => { - let lon = value - while (lon <= -180) lon += 360 - while (lon > 180) lon -= 360 - return lon - } - const latToRawV = (lat: number) => { - const clampedLat = Math.max(-mercatorLimit, Math.min(mercatorLimit, lat)) - const rad = (clampedLat * Math.PI) / 180 - return ( - 0.5 - Math.log(Math.tan(Math.PI * 0.25 + rad * 0.5)) / (2 * Math.PI) - ) - } - const maxProjectedV = latToRawV(mercatorLimit) - const minProjectedV = latToRawV(minDisplayedLatitude) - const lonLatToUV = (lon: number, lat: number) => { - const adjustedLon = normalizeLongitude(lon + baseLongitudeOffset) - const u = (adjustedLon + 180) / 360 - const adjustedLat = Math.max( - minDisplayedLatitude, - Math.min(mercatorLimit, lat + baseLatitudeOffset), - ) - const rawV = latToRawV(adjustedLat) - const normalizedV = clamp01( - (rawV - maxProjectedV) / (minProjectedV - maxProjectedV), - ) - return [u, normalizedV] as const - } - const { width, height } = canvas - const pixelRatio = window.devicePixelRatio || 1 - const worldHeight = Math.min(width / aspectRatio, height) - const worldWidth = worldHeight * aspectRatio - const panX = width * 0.5 - worldWidth * 0.5 - const panY = height * 0.5 - worldHeight * 0.5 - const [u, v] = lonLatToUV(targetLon, targetLat) - const markerY = 1 - v - const screenX = panX + u * worldWidth - const screenY = panY + markerY * worldHeight - const deviceCell = cellSize * pixelRatio - const cellX = Math.floor(screenX / deviceCell) - const cellY = Math.floor(screenY / deviceCell) - const centerX = (cellX + 0.5) * deviceCell - const centerY = (cellY + 0.5) * deviceCell - const rect = canvas.getBoundingClientRect() - const clientX = rect.left + centerX / pixelRatio - const clientY = rect.bottom - centerY / pixelRatio - return { clientX, clientY } - }) - await page.mouse.move(clientX, clientY) - await expect(tooltip).toHaveText("London GraphQL", { timeout: 5000 }) - await expect(tooltip).toBeVisible() -}) + await page.mouse.move(clientX, clientY) + await expect(tooltip).toHaveText("London GraphQL", { timeout: 5000 }) + await expect(tooltip).toBeVisible() + }, + { timeout: 60_000 }, +) From 6498a9871c3cbc4a50e2e2f63d0b81483b293e66 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 12:34:25 +0100 Subject: [PATCH 104/133] Add html reporter back --- .github/workflows/check.yml | 6 +++--- playwright.config.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 06c926b94e..62d01a817c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -49,12 +49,12 @@ jobs: - name: Run Playwright tests run: ./node_modules/.bin/playwright test - - name: Run unit tests - run: pnpm test:unit - - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: playwright-report/ retention-days: 30 + + - name: Run unit tests + run: pnpm test:unit diff --git a/playwright.config.ts b/playwright.config.ts index b594e43fe4..1a28212820 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, - reporter: process.env.CI ? "github" : "list", + reporter: process.env.CI ? [["github"], ["html"]] : "list", use: { baseURL: "http://localhost:3000", trace: "retain-on-first-failure", From d21a5926e51fa3529222709c544c4d6a4c56a3f9 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 13:12:41 +0100 Subject: [PATCH 105/133] Increase timeout and retries to work around a hydration error that happens only in test --- playwright.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 1a28212820..d252a12ad8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ outputDir: "./test/out", fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 2 : 1, workers: process.env.CI ? 1 : undefined, reporter: process.env.CI ? [["github"], ["html"]] : "list", use: { @@ -17,6 +17,8 @@ export default defineConfig({ screenshot: "only-on-failure", }, + timeout: 60 * 1000, + projects: [ { name: "chromium", From 2ad0f6076ed8ee471417a2ee1df8fb5680ea5a0b Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 13:18:33 +0100 Subject: [PATCH 106/133] Tweak tests --- test/e2e/community-events.spec.ts | 169 ++++++++++++++---------------- 1 file changed, 80 insertions(+), 89 deletions(-) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index f92017de26..b92a946ec7 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -41,93 +41,84 @@ test("map loads and Zurich meetup link works", async ({ page }) => { expect(newPage.url()).toContain("meetup.com/graphql-zurich") }) -test( - "map tooltip appears on marker hover", - async ({ page }) => { - await page.goto("/community/events") - const mapCanvas = page.locator("canvas").first() - await expect(mapCanvas).toBeVisible({ timeout: 10000 }) - await expect - .poll(async () => { - const box = await mapCanvas.boundingBox() - return Boolean(box && box.width > 100 && box.height > 100) - }) - .toBe(true) - const tooltip = page.getByRole("tooltip") - await expect(tooltip).toHaveCount(0) - await mapCanvas.hover() - const { clientX, clientY } = await page.evaluate(() => { - const canvas = document.querySelector( - "canvas", - ) as HTMLCanvasElement | null - if (!canvas) throw new Error("Canvas not found") - const targetLat = 51.51 - const targetLon = -0.12 - const aspectRatio = 1.65 - const cellSize = 8 - const mercatorLimit = 85.05112878 - const minDisplayedLatitude = -60 - const baseLatitudeOffset = 4 - const baseLongitudeOffset = 0.1 - const clamp01 = (value: number) => { - if (value <= 0) return 0 - if (value >= 1) return 1 - return value - } - const normalizeLongitude = (value: number) => { - let lon = value - while (lon <= -180) lon += 360 - while (lon > 180) lon -= 360 - return lon - } - const latToRawV = (lat: number) => { - const clampedLat = Math.max( - -mercatorLimit, - Math.min(mercatorLimit, lat), - ) - const rad = (clampedLat * Math.PI) / 180 - return ( - 0.5 - Math.log(Math.tan(Math.PI * 0.25 + rad * 0.5)) / (2 * Math.PI) - ) - } - const maxProjectedV = latToRawV(mercatorLimit) - const minProjectedV = latToRawV(minDisplayedLatitude) - const lonLatToUV = (lon: number, lat: number) => { - const adjustedLon = normalizeLongitude(lon + baseLongitudeOffset) - const u = (adjustedLon + 180) / 360 - const adjustedLat = Math.max( - minDisplayedLatitude, - Math.min(mercatorLimit, lat + baseLatitudeOffset), - ) - const rawV = latToRawV(adjustedLat) - const normalizedV = clamp01( - (rawV - maxProjectedV) / (minProjectedV - maxProjectedV), - ) - return [u, normalizedV] as const - } - const { width, height } = canvas - const pixelRatio = window.devicePixelRatio || 1 - const worldHeight = Math.min(width / aspectRatio, height) - const worldWidth = worldHeight * aspectRatio - const panX = width * 0.5 - worldWidth * 0.5 - const panY = height * 0.5 - worldHeight * 0.5 - const [u, v] = lonLatToUV(targetLon, targetLat) - const markerY = 1 - v - const screenX = panX + u * worldWidth - const screenY = panY + markerY * worldHeight - const deviceCell = cellSize * pixelRatio - const cellX = Math.floor(screenX / deviceCell) - const cellY = Math.floor(screenY / deviceCell) - const centerX = (cellX + 0.5) * deviceCell - const centerY = (cellY + 0.5) * deviceCell - const rect = canvas.getBoundingClientRect() - const clientX = rect.left + centerX / pixelRatio - const clientY = rect.bottom - centerY / pixelRatio - return { clientX, clientY } +test("map tooltip appears on marker hover", async ({ page }) => { + await page.goto("/community/events") + const mapCanvas = page.locator("canvas").first() + await expect(mapCanvas).toBeVisible({ timeout: 10000 }) + await expect + .poll(async () => { + const box = await mapCanvas.boundingBox() + return Boolean(box && box.width > 100 && box.height > 100) }) - await page.mouse.move(clientX, clientY) - await expect(tooltip).toHaveText("London GraphQL", { timeout: 5000 }) - await expect(tooltip).toBeVisible() - }, - { timeout: 60_000 }, -) + .toBe(true) + const tooltip = page.getByRole("tooltip") + await expect(tooltip).toHaveCount(0) + await mapCanvas.hover() + const { clientX, clientY } = await page.evaluate(() => { + const canvas = document.querySelector("canvas") as HTMLCanvasElement | null + if (!canvas) throw new Error("Canvas not found") + const targetLat = 51.51 + const targetLon = -0.12 + const aspectRatio = 1.65 + const cellSize = 8 + const mercatorLimit = 85.05112878 + const minDisplayedLatitude = -60 + const baseLatitudeOffset = 4 + const baseLongitudeOffset = 0.1 + const clamp01 = (value: number) => { + if (value <= 0) return 0 + if (value >= 1) return 1 + return value + } + const normalizeLongitude = (value: number) => { + let lon = value + while (lon <= -180) lon += 360 + while (lon > 180) lon -= 360 + return lon + } + const latToRawV = (lat: number) => { + const clampedLat = Math.max(-mercatorLimit, Math.min(mercatorLimit, lat)) + const rad = (clampedLat * Math.PI) / 180 + return ( + 0.5 - Math.log(Math.tan(Math.PI * 0.25 + rad * 0.5)) / (2 * Math.PI) + ) + } + const maxProjectedV = latToRawV(mercatorLimit) + const minProjectedV = latToRawV(minDisplayedLatitude) + const lonLatToUV = (lon: number, lat: number) => { + const adjustedLon = normalizeLongitude(lon + baseLongitudeOffset) + const u = (adjustedLon + 180) / 360 + const adjustedLat = Math.max( + minDisplayedLatitude, + Math.min(mercatorLimit, lat + baseLatitudeOffset), + ) + const rawV = latToRawV(adjustedLat) + const normalizedV = clamp01( + (rawV - maxProjectedV) / (minProjectedV - maxProjectedV), + ) + return [u, normalizedV] as const + } + const { width, height } = canvas + const pixelRatio = window.devicePixelRatio || 1 + const worldHeight = Math.min(width / aspectRatio, height) + const worldWidth = worldHeight * aspectRatio + const panX = width * 0.5 - worldWidth * 0.5 + const panY = height * 0.5 - worldHeight * 0.5 + const [u, v] = lonLatToUV(targetLon, targetLat) + const markerY = 1 - v + const screenX = panX + u * worldWidth + const screenY = panY + markerY * worldHeight + const deviceCell = cellSize * pixelRatio + const cellX = Math.floor(screenX / deviceCell) + const cellY = Math.floor(screenY / deviceCell) + const centerX = (cellX + 0.5) * deviceCell + const centerY = (cellY + 0.5) * deviceCell + const rect = canvas.getBoundingClientRect() + const clientX = rect.left + centerX / pixelRatio + const clientY = rect.bottom - centerY / pixelRatio + return { clientX, clientY } + }) + await page.mouse.move(clientX, clientY) + await expect(tooltip).toHaveText("London GraphQL", { timeout: 5000 }) + await expect(tooltip).toBeVisible() +}) From e25fbc50525437b7feb3b0140daa1402990a37b4 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 13:21:01 +0100 Subject: [PATCH 107/133] Add a test to check if we have webgl on CI --- test/e2e/test-test.spec.ts | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test/e2e/test-test.spec.ts diff --git a/test/e2e/test-test.spec.ts b/test/e2e/test-test.spec.ts new file mode 100644 index 0000000000..a93d392cb0 --- /dev/null +++ b/test/e2e/test-test.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from "@playwright/test" + +const currentTime = Date.now() + +const url1 = "chrome://gpu/" +const url2 = "https://www.soft8soft.com/webglreport" +const url3 = "https://webglsamples.org/aquarium/aquarium.html" + +function waitFor(delay: number) { + return new Promise(resolve => setTimeout(resolve, delay)) +} + +test.beforeEach(async (_, testInfo) => { + testInfo.setTimeout(testInfo.timeout + 160000) +}) + +test.describe("Testing 123", () => { + // Check if hardware acceleration is enabled. Without it, our tests will be much slower. + test("1. GPU hardware acceleration", async ({ page }) => { + await page.goto(url1) + const featureStatusList = page.locator(".feature-status-list") + + await waitFor(2000) + await page.screenshot({ + path: "screens/screenshot" + currentTime + "_1.png", + fullPage: true, + }) + + await expect(featureStatusList).toContainText("Hardware accelerated") + }) + + test("2. webgl report", async ({ page }) => { + await page.goto(url2) + + await waitFor(2000) + await page.screenshot({ + path: "screens/screenshot" + currentTime + "_2.png", + fullPage: true, + }) + }) + + test("3. aquarium", async ({ page }) => { + await page.goto(url3) + + await waitFor(5000) + await page.screenshot({ + path: "screens/screenshot" + currentTime + "_3.png", + fullPage: true, + }) + }) +}) From 54cd16b290f17fc432e0f7ebb1a2d14192266858 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 13:24:50 +0100 Subject: [PATCH 108/133] Add a redundant argument to satisfy Playwright --- test/e2e/test-test.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test-test.spec.ts b/test/e2e/test-test.spec.ts index a93d392cb0..a083fe9ea3 100644 --- a/test/e2e/test-test.spec.ts +++ b/test/e2e/test-test.spec.ts @@ -10,7 +10,7 @@ function waitFor(delay: number) { return new Promise(resolve => setTimeout(resolve, delay)) } -test.beforeEach(async (_, testInfo) => { +test.beforeEach(async ({ browser }, testInfo) => { testInfo.setTimeout(testInfo.timeout + 160000) }) From 92a9eb7a3a3ffb14779bfde14393890b0bf2c61d Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 13:29:14 +0100 Subject: [PATCH 109/133] Change job name to test --- .github/workflows/check.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 62d01a817c..a0a32f8324 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -26,7 +26,7 @@ jobs: - name: Validate code snippets run: pnpm validate:snippets - playwright: + test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -40,13 +40,16 @@ jobs: - name: Install Dependencies run: pnpm i + - name: Run unit tests + run: pnpm test:unit + # per the docs: "caching browser binaries is not recommended, # since the amount of time it takes to restore the cache is # comparable to the time it takes to download the binaries" - name: Install Playwright Browsers run: ./node_modules/.bin/playwright install --with-deps - - name: Run Playwright tests + - name: Run end-to-end tests run: ./node_modules/.bin/playwright test - uses: actions/upload-artifact@v4 @@ -55,6 +58,3 @@ jobs: name: playwright-report path: playwright-report/ retention-days: 30 - - - name: Run unit tests - run: pnpm test:unit From 904f2e54d9e5b208a692550641c9188d97f8a1af Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 13:49:35 +0100 Subject: [PATCH 110/133] Add a button to add events to the UI --- src/app/(main)/community/events/page.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index e966fdbbc2..af42675ab4 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -1,5 +1,6 @@ import { Breadcrumbs } from "@/_design-system/breadcrumbs" import { meetups } from "@/components/meetups" +import { Button } from "@/app/conf/_design-system/button" import { MeetupsMap } from "./meetups-map" import { EventsList } from "./events-list" @@ -9,6 +10,10 @@ import { GetYourMeetupNoticedSection } from "./get-your-meetup-noticed-section" import { BringGraphQLToYourCommunity } from "./bring-graphql-to-your-community" import dynamic from "next/dynamic" +// TODO: The issue template should probably live in https://github.com/graphql/community-wg +const ISSUE_TEMPLATE_LINK = + "https://github.com/graphql/graphql.github.io/issues/new?assignees=&labels=event&template=event-submission.yml" + const GalleryStrip = dynamic( () => import("@/app/conf/2025/components/gallery-strip").then( @@ -75,10 +80,20 @@ export default function EventsPage() { {upcomingEvents.length > 0 && (
-

Upcoming events

-

- See what’s coming up across the GraphQL ecosystem. -

+
+
+

Upcoming events

+

+ See what’s coming up across the GraphQL ecosystem. +

+
+ +
)} From e15367a651bc50bd199f1852e39f7079c9dd2a4e Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 14:02:46 +0100 Subject: [PATCH 111/133] Add an issue template --- .github/ISSUE_TEMPLATE/event-submission.yml | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/event-submission.yml diff --git a/.github/ISSUE_TEMPLATE/event-submission.yml b/.github/ISSUE_TEMPLATE/event-submission.yml new file mode 100644 index 0000000000..cb111130ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/event-submission.yml @@ -0,0 +1,89 @@ +name: Event submission +description: Share a meetup or conference with the GraphQL community +title: "Event submission: " +labels: + - event +body: + - type: markdown + attributes: + value: > + Tell us about the next moment your corner of the GraphQL ecosystem gathers. + These details feed the Events & Meetups page so more folks can find you and show up. + - type: dropdown + id: event_type + attributes: + label: What kind of gathering is it? + options:desc + - Meetup + - Conference + validations: + required: true + - type: input + id: event_name + attributes: + label: Event name + placeholder: GraphQL Amsterdam + validations: + required: true + - type: textarea + id: event_summary + attributes: + label: What should people expect? + placeholder: A full day of GraphQL deep dives and hallway conversations in Amsterdam. + - type: input + id: event_start + attributes: + label: Start date and time + description: Include the timezone or UTC offset, for example 2025-09-08 09:00 CEST. + placeholder: 2025-09-08 09:00 CEST + validations: + required: true + - type: input + id: event_end + attributes: + label: End date or final session time + description: Useful for multi-day conferences or evening meetups with an end time. + placeholder: 2025-09-10 17:00 CEST + - type: input + id: location + attributes: + label: City and country (or Online) + placeholder: Amsterdam, Netherlands + validations: + required: true + - type: input + id: venue + attributes: + label: Venue or neighborhood + description: Optional context that helps locals plan their trip. + placeholder: Beurs van Berlage + - type: input + id: event_link + attributes: + label: Primary event or registration link + placeholder: https:// + validations: + required: true + - type: input + id: host_name + attributes: + label: Host organization or meetup group + placeholder: GraphQL Foundation + validations: + required: true + - type: input + id: host_link + attributes: + label: Host link + description: Website, meetup page, or social profile. + placeholder: https://www.meetup.com/amsterdam-graphql-meetup/ + - type: input + id: contact + attributes: + label: Contact email or Discord username + placeholder: john.doe@example.com + - type: textarea + id: extras + attributes: + label: Anything else we should know? + description: CFP deadlines, sponsors, accessibility notes, or other context. From 1607067da3a4700d4c1978c15c136d1a9ce61d3c Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 14:06:17 +0100 Subject: [PATCH 112/133] Improve the event submission issue template --- .github/ISSUE_TEMPLATE/event-submission.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/event-submission.yml b/.github/ISSUE_TEMPLATE/event-submission.yml index cb111130ea..39284298eb 100644 --- a/.github/ISSUE_TEMPLATE/event-submission.yml +++ b/.github/ISSUE_TEMPLATE/event-submission.yml @@ -7,13 +7,13 @@ body: - type: markdown attributes: value: > - Tell us about the next moment your corner of the GraphQL ecosystem gathers. - These details feed the Events & Meetups page so more folks can find you and show up. + Share your upcoming GraphQL gathering. + These details will be displayed on the Events & Meetups page so more people can find you. - type: dropdown id: event_type attributes: label: What kind of gathering is it? - options:desc + options: - Meetup - Conference validations: From 99d9f6dee8d52e2b09cfb42a87c90faf7a68a7ac Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 14:14:06 +0100 Subject: [PATCH 113/133] Change Playwright run args --- playwright.config.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index d252a12ad8..1908bc55d3 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,7 +22,12 @@ export default defineConfig({ projects: [ { name: "chromium", - use: { ...devices["Desktop Chrome"] }, + use: { + ...devices["Desktop Chrome"], + launchOptions: { + args: ["--use-gl=egl"], + }, + }, }, ], From 40252709322bd66716a540fdb4519e5d9f773c2b Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 14:45:38 +0100 Subject: [PATCH 114/133] Fiddle with tests --- test/e2e/community-events.spec.ts | 2 +- test/e2e/test-test.spec.ts | 18 ++---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index b92a946ec7..f68e8028ec 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -18,7 +18,7 @@ test("map loads and Zurich meetup link works", async ({ page }) => { // Take a screenshot of the map and verify it matches snapshot const mapContainer = page.locator("canvas").first() await expect(mapContainer).toHaveScreenshot("meetups-map.png", { - timeout: 10000, + timeout: 20_000, }) // Find the "Past events & meetups" section diff --git a/test/e2e/test-test.spec.ts b/test/e2e/test-test.spec.ts index a083fe9ea3..53b8a342ec 100644 --- a/test/e2e/test-test.spec.ts +++ b/test/e2e/test-test.spec.ts @@ -15,26 +15,12 @@ test.beforeEach(async ({ browser }, testInfo) => { }) test.describe("Testing 123", () => { - // Check if hardware acceleration is enabled. Without it, our tests will be much slower. - test("1. GPU hardware acceleration", async ({ page }) => { - await page.goto(url1) - const featureStatusList = page.locator(".feature-status-list") - - await waitFor(2000) - await page.screenshot({ - path: "screens/screenshot" + currentTime + "_1.png", - fullPage: true, - }) - - await expect(featureStatusList).toContainText("Hardware accelerated") - }) - test("2. webgl report", async ({ page }) => { await page.goto(url2) await waitFor(2000) await page.screenshot({ - path: "screens/screenshot" + currentTime + "_2.png", + path: "playwright-report/screenshot" + currentTime + "_2.png", fullPage: true, }) }) @@ -44,7 +30,7 @@ test.describe("Testing 123", () => { await waitFor(5000) await page.screenshot({ - path: "screens/screenshot" + currentTime + "_3.png", + path: "playwright-report/screenshot" + currentTime + "_3.png", fullPage: true, }) }) From 3a40b7d3e9b3ea7cbc32e0c00d70b16c0c64bcf2 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 14:57:06 +0100 Subject: [PATCH 115/133] Move the issue template to community-wg repo --- .github/ISSUE_TEMPLATE/event-submission.yml | 89 --------------------- src/app/(main)/community/events/page.tsx | 3 +- 2 files changed, 1 insertion(+), 91 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/event-submission.yml diff --git a/.github/ISSUE_TEMPLATE/event-submission.yml b/.github/ISSUE_TEMPLATE/event-submission.yml deleted file mode 100644 index 39284298eb..0000000000 --- a/.github/ISSUE_TEMPLATE/event-submission.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Event submission -description: Share a meetup or conference with the GraphQL community -title: "Event submission: " -labels: - - event -body: - - type: markdown - attributes: - value: > - Share your upcoming GraphQL gathering. - These details will be displayed on the Events & Meetups page so more people can find you. - - type: dropdown - id: event_type - attributes: - label: What kind of gathering is it? - options: - - Meetup - - Conference - validations: - required: true - - type: input - id: event_name - attributes: - label: Event name - placeholder: GraphQL Amsterdam - validations: - required: true - - type: textarea - id: event_summary - attributes: - label: What should people expect? - placeholder: A full day of GraphQL deep dives and hallway conversations in Amsterdam. - - type: input - id: event_start - attributes: - label: Start date and time - description: Include the timezone or UTC offset, for example 2025-09-08 09:00 CEST. - placeholder: 2025-09-08 09:00 CEST - validations: - required: true - - type: input - id: event_end - attributes: - label: End date or final session time - description: Useful for multi-day conferences or evening meetups with an end time. - placeholder: 2025-09-10 17:00 CEST - - type: input - id: location - attributes: - label: City and country (or Online) - placeholder: Amsterdam, Netherlands - validations: - required: true - - type: input - id: venue - attributes: - label: Venue or neighborhood - description: Optional context that helps locals plan their trip. - placeholder: Beurs van Berlage - - type: input - id: event_link - attributes: - label: Primary event or registration link - placeholder: https:// - validations: - required: true - - type: input - id: host_name - attributes: - label: Host organization or meetup group - placeholder: GraphQL Foundation - validations: - required: true - - type: input - id: host_link - attributes: - label: Host link - description: Website, meetup page, or social profile. - placeholder: https://www.meetup.com/amsterdam-graphql-meetup/ - - type: input - id: contact - attributes: - label: Contact email or Discord username - placeholder: john.doe@example.com - - type: textarea - id: extras - attributes: - label: Anything else we should know? - description: CFP deadlines, sponsors, accessibility notes, or other context. diff --git a/src/app/(main)/community/events/page.tsx b/src/app/(main)/community/events/page.tsx index af42675ab4..1d2821a835 100644 --- a/src/app/(main)/community/events/page.tsx +++ b/src/app/(main)/community/events/page.tsx @@ -10,9 +10,8 @@ import { GetYourMeetupNoticedSection } from "./get-your-meetup-noticed-section" import { BringGraphQLToYourCommunity } from "./bring-graphql-to-your-community" import dynamic from "next/dynamic" -// TODO: The issue template should probably live in https://github.com/graphql/community-wg const ISSUE_TEMPLATE_LINK = - "https://github.com/graphql/graphql.github.io/issues/new?assignees=&labels=event&template=event-submission.yml" + "https://github.com/graphql/community-wg/issues/new?assignees=&labels=event&template=event-submission.yml" const GalleryStrip = dynamic( () => From e6c1de028d62b42340421af832adda387733cef0 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 15:05:51 +0100 Subject: [PATCH 116/133] Fight Playwright --- .../(main)/community/events/meetups-map.tsx | 1 + test/e2e/community-events.spec.ts | 30 +++++++++---------- test/e2e/test-test.spec.ts | 15 ++-------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/app/(main)/community/events/meetups-map.tsx b/src/app/(main)/community/events/meetups-map.tsx index 25ead598e5..8724d83e6b 100644 --- a/src/app/(main)/community/events/meetups-map.tsx +++ b/src/app/(main)/community/events/meetups-map.tsx @@ -140,6 +140,7 @@ export function MeetupsMap() { return (
{ setActiveMeetupId(null) }} diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index f68e8028ec..f0876bef47 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -3,11 +3,9 @@ import { test, expect } from "@playwright/test" test("map loads and Zurich meetup link works", async ({ page }) => { await page.goto("/community/events") - // Wait for the map canvas to be visible const mapCanvas = page.locator("canvas").first() await expect(mapCanvas).toBeVisible({ timeout: 10000 }) - // Wait for map to finish loading by checking if it has proper dimensions await expect .poll(async () => { const box = await mapCanvas.boundingBox() @@ -15,24 +13,20 @@ test("map loads and Zurich meetup link works", async ({ page }) => { }) .toBe(true) - // Take a screenshot of the map and verify it matches snapshot - const mapContainer = page.locator("canvas").first() - await expect(mapContainer).toHaveScreenshot("meetups-map.png", { - timeout: 20_000, - }) + const mapContainer = page.locator("#meetups-map") + await mapContainer.scrollIntoViewIfNeeded() + await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( + "meetups-map.png", + { timeout: 10_000 }, + ) - // Find the "Past events & meetups" section const pastEventsSection = page.locator("text=Past events & meetups") await pastEventsSection.scrollIntoViewIfNeeded() - // Find the scrollview container with past events and meetups - - // Find the Zurich meetup card in the scrollable list (not the map popup) const link = page.getByRole("link", { name: /Zurich/i }).first() await link.scrollIntoViewIfNeeded() await link.click() - // Click the link and verify it opens to the correct URL const pagePromise = page.context().waitForEvent("page") await link.click() const newPage = await pagePromise @@ -43,19 +37,23 @@ test("map loads and Zurich meetup link works", async ({ page }) => { test("map tooltip appears on marker hover", async ({ page }) => { await page.goto("/community/events") - const mapCanvas = page.locator("canvas").first() - await expect(mapCanvas).toBeVisible({ timeout: 10000 }) + const mapContainer = page.locator("#meetups-map").first() + await mapContainer.scrollIntoViewIfNeeded() + await expect(mapContainer).toBeVisible({ timeout: 10000 }) await expect .poll(async () => { - const box = await mapCanvas.boundingBox() + const box = await mapContainer.boundingBox() return Boolean(box && box.width > 100 && box.height > 100) }) .toBe(true) const tooltip = page.getByRole("tooltip") await expect(tooltip).toHaveCount(0) + const mapCanvas = mapContainer.locator("canvas").first() await mapCanvas.hover() const { clientX, clientY } = await page.evaluate(() => { - const canvas = document.querySelector("canvas") as HTMLCanvasElement | null + const canvas = document.querySelector( + "#meetups-map canvas", + ) as HTMLCanvasElement | null if (!canvas) throw new Error("Canvas not found") const targetLat = 51.51 const targetLon = -0.12 diff --git a/test/e2e/test-test.spec.ts b/test/e2e/test-test.spec.ts index 53b8a342ec..6a4fb2b09a 100644 --- a/test/e2e/test-test.spec.ts +++ b/test/e2e/test-test.spec.ts @@ -1,8 +1,5 @@ import { test, expect } from "@playwright/test" -const currentTime = Date.now() - -const url1 = "chrome://gpu/" const url2 = "https://www.soft8soft.com/webglreport" const url3 = "https://webglsamples.org/aquarium/aquarium.html" @@ -14,24 +11,18 @@ test.beforeEach(async ({ browser }, testInfo) => { testInfo.setTimeout(testInfo.timeout + 160000) }) -test.describe("Testing 123", () => { +test.describe("Testing WebGL Support", () => { test("2. webgl report", async ({ page }) => { await page.goto(url2) await waitFor(2000) - await page.screenshot({ - path: "playwright-report/screenshot" + currentTime + "_2.png", - fullPage: true, - }) + expect(page).toHaveScreenshot("webgl-report.png") }) test("3. aquarium", async ({ page }) => { await page.goto(url3) await waitFor(5000) - await page.screenshot({ - path: "playwright-report/screenshot" + currentTime + "_3.png", - fullPage: true, - }) + expect(page).toHaveScreenshot("aquarium.png") }) }) From 48fb8a999159620b4729ec0c74d2794f5e99456e Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 15:07:50 +0100 Subject: [PATCH 117/133] Tinker with Chrome flags --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 1908bc55d3..938d9a841e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"], launchOptions: { - args: ["--use-gl=egl"], + args: ["--enable-gpu"], }, }, }, From 7782253c964b1d5b940153fb6f461cb0a345ec38 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 15:20:50 +0100 Subject: [PATCH 118/133] Split e2e and unit tests to separate jobs --- .github/workflows/check.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a0a32f8324..3b738b3cde 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -26,7 +26,7 @@ jobs: - name: Validate code snippets run: pnpm validate:snippets - test: + unit-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -43,6 +43,20 @@ jobs: - name: Run unit tests run: pnpm test:unit + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: the-guild-org/shared-config/setup@main + name: setup env + with: + packageManager: pnpm + workingDirectory: ./ + + - name: Install Dependencies + run: pnpm i + # per the docs: "caching browser binaries is not recommended, # since the amount of time it takes to restore the cache is # comparable to the time it takes to download the binaries" From b70b7f631b3f94acd7290df94e3451d8313aaa77 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 15:20:53 +0100 Subject: [PATCH 119/133] Use ANGLE --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 938d9a841e..efb89809be 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"], launchOptions: { - args: ["--enable-gpu"], + args: process.env.CI ? ["--enable-gpu", "--use-gl=angle"] : [], }, }, }, From 1ecd30ecd71f23ee4f26e847a408ab3ab3c19756 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 15:38:49 +0100 Subject: [PATCH 120/133] Get tests to work locally again --- playwright.config.ts | 7 +------ test/e2e/community-events.spec.ts | 2 +- test/e2e/test-test.spec.ts | 28 ---------------------------- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 test/e2e/test-test.spec.ts diff --git a/playwright.config.ts b/playwright.config.ts index efb89809be..d252a12ad8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,12 +22,7 @@ export default defineConfig({ projects: [ { name: "chromium", - use: { - ...devices["Desktop Chrome"], - launchOptions: { - args: process.env.CI ? ["--enable-gpu", "--use-gl=angle"] : [], - }, - }, + use: { ...devices["Desktop Chrome"] }, }, ], diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index f0876bef47..839609ad96 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -14,7 +14,7 @@ test("map loads and Zurich meetup link works", async ({ page }) => { .toBe(true) const mapContainer = page.locator("#meetups-map") - await mapContainer.scrollIntoViewIfNeeded() + await mapContainer.focus() await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( "meetups-map.png", { timeout: 10_000 }, diff --git a/test/e2e/test-test.spec.ts b/test/e2e/test-test.spec.ts deleted file mode 100644 index 6a4fb2b09a..0000000000 --- a/test/e2e/test-test.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test, expect } from "@playwright/test" - -const url2 = "https://www.soft8soft.com/webglreport" -const url3 = "https://webglsamples.org/aquarium/aquarium.html" - -function waitFor(delay: number) { - return new Promise(resolve => setTimeout(resolve, delay)) -} - -test.beforeEach(async ({ browser }, testInfo) => { - testInfo.setTimeout(testInfo.timeout + 160000) -}) - -test.describe("Testing WebGL Support", () => { - test("2. webgl report", async ({ page }) => { - await page.goto(url2) - - await waitFor(2000) - expect(page).toHaveScreenshot("webgl-report.png") - }) - - test("3. aquarium", async ({ page }) => { - await page.goto(url3) - - await waitFor(5000) - expect(page).toHaveScreenshot("aquarium.png") - }) -}) From 929393b9d46c835ed35908c4a33ad625714cd262 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 15:49:26 +0100 Subject: [PATCH 121/133] Gitignore local .pnpm-store --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2c2af0a81e..9c5209d9cc 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ out/ tsconfig.tsbuildinfo playwright-report/ + +.pnpm-store/ From 5e1ba8cef35efdfe9a18fa46d0aa14d9e2e8e4a0 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 16:08:08 +0100 Subject: [PATCH 122/133] Try channel Chromium --- playwright.config.ts | 2 +- .../meetups-map-chromium-linux.png | Bin 0 -> 4056 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 test/e2e/community-events.spec.ts-snapshots/meetups-map-chromium-linux.png diff --git a/playwright.config.ts b/playwright.config.ts index d252a12ad8..90798554a6 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ projects: [ { name: "chromium", - use: { ...devices["Desktop Chrome"] }, + use: { ...devices["Desktop Chrome"], channel: "chromium" }, }, ], diff --git a/test/e2e/community-events.spec.ts-snapshots/meetups-map-chromium-linux.png b/test/e2e/community-events.spec.ts-snapshots/meetups-map-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a1f768bcee1d2e6fb52bb9463008181be67d3771 GIT binary patch literal 4056 zcmd5LW_xs7)S*+)ltyu;Dz;l^z?D-V{Ba0?q7Yo0aMGLy^y6a$Y&bqJ!d^m){N{(BIc!mO1^PQIlh;hl;Yc!d%G_PQ2O zWoca9-_A)YG2TaVdg|165{aSjV~1ONIdTJId^!N`h*3Qj{{GMckyxXTQ>ENR6K$cn zpT)j>I-lLrFva*h>z$lh{FK0rB9aPqQnEkTNG3}C0pWL)_ig4ZD}xf0Ih%BFkAZ033^rP)&avA9&)@_G#E2E%pv z<^^7KNaIw%5U9v|fSg(0cHSxsRK;h?+CmxLZJD$|jUC%8j(;e)o#)*7PQ52RxJJ$N z-FtfL9A$=d(Jh(p9mo9>-0?asXfZ1eGjd0QuHd7!VuG=;H~wE~1$CM&qexrA`cf~pH(M|jJjS9ae_(o z?YGG}CLY35wk=pilx;>jT-qn+fc$H5Y@p;LO>K{Yx7beciJlTn`Wi9WUl7$+w5%hF+@KV`glAyYJFV9ax3UNW0_(lgkpHdtZsRT!$SwF=0+pHXK#Ec!y_av! zBVob9bQ%Y{I0)sMY_RRBE+k$hjVP6#gAgb>(fNV>WWi=`V)u%b_}^U}r~WBbc`rc8 z4|#a3K^ftEqTLH;Qojp)5A;_jkHHVCBo*#D^;I5Cbn;Cy_38w}=tlV62OwLen6#>b zbR>B`d?^V}w>LsZnQ~lYm3nrK1iHK|#w@?gEK|q#XHLcNhTJ20=m%C6N1LUx6}%!C zsdn?sJ4v8LHCr-Uo4#2rzMhRW4L42_j-f1yi3CZySMbd9H&(z^Z69(C6a?FsN5Wb= zo7k8a^=}_s$D5lj+k`#q{HrI{Y85e(63Rer+unTV!G_Ik#UHp@l@I$JBJqa?s!S@= z_n5r89cqQ0#2B(42ho$o%V`4VS|hv1BVn|9h5d8M_B-*%P~nRL{MJ!feS=~Lj@Qi2 zLbJxQ`cHSq*Mz)^joo^*UCggMY=A`tvP$)RGPKk*?7npYMMS54XZj(VTgM_1i4CtS z`~gVM3tJWvwk&A$Wb>@Vf9N2YZ49h+Auw|39P*pzQG-U;XR=Msl$zaF7y zNxEpGrPQP+stBCKx|ma#2yy z=QjAPe+<&gdDk$18RWY{g7=N+=reV{#vrddpp0=qJ-G!Iz8G@b)i6aSCrLSw(cEcE zA(Q*N%*67(ryv<7FtW{LA`Qzzu2PplqL~g1LJ3F6oFq|OK#Y82On?|r#9%iD$}x2J zKfh(~$(eJHh{Zs8wJda&6U0ip@P*KiABGR}1c1;^zS_T-u|D(!$b3C(PyH{C{Py3k CeDI|J literal 0 HcmV?d00001 From e0025d00491d6fd2627be41fec12f413b2b68873 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 16:31:09 +0100 Subject: [PATCH 123/133] Actually visit the page before test --- playwright.config.ts | 2 +- test/e2e/community-events.spec.ts | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 90798554a6..692993c7e1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ screenshot: "only-on-failure", }, - timeout: 60 * 1000, + timeout: 35 * 1000, projects: [ { diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index 839609ad96..cbf3c58931 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -1,8 +1,10 @@ import { test, expect } from "@playwright/test" -test("map loads and Zurich meetup link works", async ({ page }) => { +test.beforeEach(async ({ page }) => { await page.goto("/community/events") +}) +test("Zurich meetup link works", async ({ page }) => { const mapCanvas = page.locator("canvas").first() await expect(mapCanvas).toBeVisible({ timeout: 10000 }) @@ -15,16 +17,13 @@ test("map loads and Zurich meetup link works", async ({ page }) => { const mapContainer = page.locator("#meetups-map") await mapContainer.focus() - await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( - "meetups-map.png", - { timeout: 10_000 }, - ) const pastEventsSection = page.locator("text=Past events & meetups") await pastEventsSection.scrollIntoViewIfNeeded() const link = page.getByRole("link", { name: /Zurich/i }).first() await link.scrollIntoViewIfNeeded() + await link.click() const pagePromise = page.context().waitForEvent("page") @@ -35,8 +34,16 @@ test("map loads and Zurich meetup link works", async ({ page }) => { expect(newPage.url()).toContain("meetup.com/graphql-zurich") }) +test("map matches screenshot", async ({ page }) => { + const mapContainer = page.locator("#meetups-map").first() + await mapContainer.scrollIntoViewIfNeeded() + await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( + "meetups-map.png", + { timeout: 15_000 }, + ) +}) + test("map tooltip appears on marker hover", async ({ page }) => { - await page.goto("/community/events") const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded() await expect(mapContainer).toBeVisible({ timeout: 10000 }) From 501479dd6630c41f76024135619631d45f983216 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 16:39:00 +0100 Subject: [PATCH 124/133] Increase the timeout to 45 seconds --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 692993c7e1..95f5240ef0 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ screenshot: "only-on-failure", }, - timeout: 35 * 1000, + timeout: 45 * 1000, projects: [ { From 0554fcf108b21f27f954d60ca87bd2cc6d09aa90 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 16:39:07 +0100 Subject: [PATCH 125/133] Go back to old job name --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3b738b3cde..efe878da38 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -43,7 +43,7 @@ jobs: - name: Run unit tests run: pnpm test:unit - e2e: + playwright: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 8c0cd4078809f1543d177204467f415d43205060 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 16:40:47 +0100 Subject: [PATCH 126/133] Increase the timeout to 60 seconds --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 95f5240ef0..90798554a6 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ screenshot: "only-on-failure", }, - timeout: 45 * 1000, + timeout: 60 * 1000, projects: [ { From 49afd642d59b4d3d2f28d6c612432d0b5c628fa8 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 16:41:09 +0100 Subject: [PATCH 127/133] Wait until Playwright stops scrolling until doing something --- test/e2e/community-events.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index cbf3c58931..b6e8a7c091 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -37,6 +37,8 @@ test("Zurich meetup link works", async ({ page }) => { test("map matches screenshot", async ({ page }) => { const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded() + await expect(mapContainer).toBeVisible({ timeout: 10000 }) + await page.waitForTimeout(1500) // we need to wait until Playwright finishes scrolling... await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( "meetups-map.png", { timeout: 15_000 }, @@ -57,6 +59,7 @@ test("map tooltip appears on marker hover", async ({ page }) => { await expect(tooltip).toHaveCount(0) const mapCanvas = mapContainer.locator("canvas").first() await mapCanvas.hover() + await page.waitForTimeout(2000) // we need to wait until Playwright finishes scrolling... const { clientX, clientY } = await page.evaluate(() => { const canvas = document.querySelector( "#meetups-map canvas", From 5147d4dd74731570d48c52c3a823bf836b5defd3 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 17:19:56 +0100 Subject: [PATCH 128/133] Remove redundant line from test --- test/e2e/community-events.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index b6e8a7c091..ed583c4049 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -15,10 +15,7 @@ test("Zurich meetup link works", async ({ page }) => { }) .toBe(true) - const mapContainer = page.locator("#meetups-map") - await mapContainer.focus() - - const pastEventsSection = page.locator("text=Past events & meetups") + const pastEventsSection = page.getByText("Past events & meetups") await pastEventsSection.scrollIntoViewIfNeeded() const link = page.getByRole("link", { name: /Zurich/i }).first() From dc996d29ca28229b53ddc5a999e3c12dd556a5fe Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 17:40:48 +0100 Subject: [PATCH 129/133] Try adding flags --- playwright.config.ts | 10 +++++++++- test/e2e/community-events.spec.ts | 24 +++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 90798554a6..2cf94a4dc4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,7 +22,15 @@ export default defineConfig({ projects: [ { name: "chromium", - use: { ...devices["Desktop Chrome"], channel: "chromium" }, + use: { + ...devices["Desktop Chrome"], + channel: "chromium", + ...(process.env.CI + ? { + args: ["--use-gl=egl", "--ignore-gpu-blocklist"], + } + : {}), + }, }, ], diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index ed583c4049..6e3d6343aa 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -5,19 +5,6 @@ test.beforeEach(async ({ page }) => { }) test("Zurich meetup link works", async ({ page }) => { - const mapCanvas = page.locator("canvas").first() - await expect(mapCanvas).toBeVisible({ timeout: 10000 }) - - await expect - .poll(async () => { - const box = await mapCanvas.boundingBox() - return box && box.width > 100 && box.height > 100 - }) - .toBe(true) - - const pastEventsSection = page.getByText("Past events & meetups") - await pastEventsSection.scrollIntoViewIfNeeded() - const link = page.getByRole("link", { name: /Zurich/i }).first() await link.scrollIntoViewIfNeeded() @@ -36,6 +23,17 @@ test("map matches screenshot", async ({ page }) => { await mapContainer.scrollIntoViewIfNeeded() await expect(mapContainer).toBeVisible({ timeout: 10000 }) await page.waitForTimeout(1500) // we need to wait until Playwright finishes scrolling... + + const mapCanvas = page.locator("canvas").first() + await expect(mapCanvas).toBeVisible({ timeout: 10000 }) + + await expect + .poll(async () => { + const box = await mapCanvas.boundingBox() + return box && box.width > 100 && box.height > 100 + }) + .toBe(true) + await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( "meetups-map.png", { timeout: 15_000 }, From 8001f6d9b13ac2071043aa8c3a43031e48dfb862 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 18:06:39 +0100 Subject: [PATCH 130/133] Once again try to run on CI, this time with test.slow() --- .github/workflows/check.yml | 2 +- playwright.config.ts | 7 ++++++- test/e2e/community-events.spec.ts | 21 ++++++++++++++------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index efe878da38..206e3a04d8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -1,4 +1,4 @@ -name: Lint and check formatting +name: lint & test on: pull_request diff --git a/playwright.config.ts b/playwright.config.ts index 2cf94a4dc4..109181d686 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -27,7 +27,12 @@ export default defineConfig({ channel: "chromium", ...(process.env.CI ? { - args: ["--use-gl=egl", "--ignore-gpu-blocklist"], + args: [ + "--use-gl=angle", + "--use-angle=gl-egl", + "--ignore-gpu-blocklist", + "--enable-unsafe-swiftshader", + ], } : {}), }, diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index 6e3d6343aa..d66311750d 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -5,6 +5,8 @@ test.beforeEach(async ({ page }) => { }) test("Zurich meetup link works", async ({ page }) => { + if (process.env.CI) test.slow() + const link = page.getByRole("link", { name: /Zurich/i }).first() await link.scrollIntoViewIfNeeded() @@ -19,28 +21,33 @@ test("Zurich meetup link works", async ({ page }) => { }) test("map matches screenshot", async ({ page }) => { + if (process.env.CI) test.slow() + const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded() - await expect(mapContainer).toBeVisible({ timeout: 10000 }) await page.waitForTimeout(1500) // we need to wait until Playwright finishes scrolling... const mapCanvas = page.locator("canvas").first() - await expect(mapCanvas).toBeVisible({ timeout: 10000 }) await expect - .poll(async () => { - const box = await mapCanvas.boundingBox() - return box && box.width > 100 && box.height > 100 - }) + .poll( + async () => { + const box = await mapCanvas.boundingBox() + return box && box.width > 100 && box.height > 100 + }, + { timeout: 15_000 }, + ) .toBe(true) await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( "meetups-map.png", - { timeout: 15_000 }, + { timeout: 30_000 }, ) }) test("map tooltip appears on marker hover", async ({ page }) => { + if (process.env.CI) test.slow() + const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded() await expect(mapContainer).toBeVisible({ timeout: 10000 }) From adf8c7c631ab373879af8430c3d89891979259e0 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 18:34:31 +0100 Subject: [PATCH 131/133] Do not use .poll on CI --- test/e2e/community-events.spec.ts | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index d66311750d..9174e6227b 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -29,15 +29,17 @@ test("map matches screenshot", async ({ page }) => { const mapCanvas = page.locator("canvas").first() - await expect - .poll( - async () => { - const box = await mapCanvas.boundingBox() - return box && box.width > 100 && box.height > 100 - }, - { timeout: 15_000 }, - ) - .toBe(true) + if (!process.env.CI) { + await expect + .poll( + async () => { + const box = await mapCanvas.boundingBox() + return box && box.width > 100 && box.height > 100 + }, + { timeout: 15_000 }, + ) + .toBe(true) + } await expect(mapContainer.locator("canvas").first()).toHaveScreenshot( "meetups-map.png", @@ -51,12 +53,16 @@ test("map tooltip appears on marker hover", async ({ page }) => { const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded() await expect(mapContainer).toBeVisible({ timeout: 10000 }) - await expect - .poll(async () => { - const box = await mapContainer.boundingBox() - return Boolean(box && box.width > 100 && box.height > 100) - }) - .toBe(true) + + if (!process.env.CI) { + await expect + .poll(async () => { + const box = await mapContainer.boundingBox() + return Boolean(box && box.width > 100 && box.height > 100) + }) + .toBe(true) + } + const tooltip = page.getByRole("tooltip") await expect(tooltip).toHaveCount(0) const mapCanvas = mapContainer.locator("canvas").first() From d941a5035f70315624d7c80f071b68c327bf4863 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 18:37:29 +0100 Subject: [PATCH 132/133] Add --enable-gpu flag --- playwright.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/playwright.config.ts b/playwright.config.ts index 109181d686..1d32e2a082 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -28,6 +28,7 @@ export default defineConfig({ ...(process.env.CI ? { args: [ + "--enable-gpu", "--use-gl=angle", "--use-angle=gl-egl", "--ignore-gpu-blocklist", From fac227cf3da3c4f0210b2ae56cdcc127edfa3971 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Fri, 14 Nov 2025 19:21:29 +0100 Subject: [PATCH 133/133] Give up. It seems everything works it just times out on CI --- test/e2e/community-events.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/community-events.spec.ts b/test/e2e/community-events.spec.ts index 9174e6227b..e0bd7e632c 100644 --- a/test/e2e/community-events.spec.ts +++ b/test/e2e/community-events.spec.ts @@ -5,7 +5,7 @@ test.beforeEach(async ({ page }) => { }) test("Zurich meetup link works", async ({ page }) => { - if (process.env.CI) test.slow() + if (process.env.CI) test.skip() const link = page.getByRole("link", { name: /Zurich/i }).first() await link.scrollIntoViewIfNeeded() @@ -21,7 +21,7 @@ test("Zurich meetup link works", async ({ page }) => { }) test("map matches screenshot", async ({ page }) => { - if (process.env.CI) test.slow() + if (process.env.CI) test.skip() const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded() @@ -48,7 +48,7 @@ test("map matches screenshot", async ({ page }) => { }) test("map tooltip appears on marker hover", async ({ page }) => { - if (process.env.CI) test.slow() + if (process.env.CI) test.skip() const mapContainer = page.locator("#meetups-map").first() await mapContainer.scrollIntoViewIfNeeded()