+ )
+}
+
+function MailIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
+ return (
+
+ )
+}
+
+export const metadata: Metadata = {
+ title: 'About',
+ description:
+ 'I’m dr. Jip J. Dekker, a problem solver using optimization technologies.',
+}
+
+export default function About() {
+ return (
+
+
+
+
+
+
+
+
+
+ I’m dr. Jip J. Dekker, a problem solver using optimization
+ technologies.
+
+
+
+ I’m a computer scientist at Monash University specializing in
+ optimization and devoted to developing cutting-edge tools and
+ modelling languages that simplify the process of solving complex
+ problems. With a passion for advancing the field, I contribute to
+ the optimization field, enhancing its efficiency, and facilitating
+ its practical application.
+
+
+ Building from a foundation in constraint programming, I have
+ gained experience in Boolean satisfiability, mathematical
+ modelling, local search, various hybrid method. With the wide
+ range of optimization technologies at my fingertips, it enables me
+ to approach optimization challenges from many angles. This allows
+ me to facilitate the creation of innovative software solutions
+ that streamline the formulation, solution, and analysis of
+ optimization problems.
+
+
+ At the heart of my work is a commitment to making optimization
+ more accessible and user-friendly. By developing intuitive tools
+ and modelling languages, I want to empower researchers,
+ practitioners, and anyone willing to learn to effectively address
+ complex optimization challenges across a wide range of domains. I
+ hope that my work not only enhances the effectiveness of
+ optimization technologies, but also fosters innovation and
+ improvements in other industries.
+
+
+ When I’m not solving optimization problems, I love bird watching,
+ music, and cycling. The beauty of the avian world has captivated
+ me ever since I moved to Australia. Music brings me solace. I’m
+ always looking for new songs with interesting melodies or striking
+ lyrics. Cycling always gives me a sense of freedom. You find the
+ most wonderful places, if you stray from the beaten track even
+ slightly. These pursuits bring me joy and inspiration.
+
+
+
+
+
+
+ Follow me on GitHub
+
+
+ Follow me on Mastodon
+
+
+ Connect with me on LinkedIn
+
+
+ Find me on ORCID
+
+
+ Follow me on ResearchGate
+
+
+ jip.dekker@monash.edu
+
+
+
+
+
+ )
+}
diff --git a/src/pages/articles/2017-02-custom-dtrace-instrumentation.mdx b/src/app/articles/2017-02-custom-dtrace-instrumentation/page.mdx
similarity index 97%
rename from src/pages/articles/2017-02-custom-dtrace-instrumentation.mdx
rename to src/app/articles/2017-02-custom-dtrace-instrumentation/page.mdx
index 5d29cf5..78b1181 100644
--- a/src/pages/articles/2017-02-custom-dtrace-instrumentation.mdx
+++ b/src/app/articles/2017-02-custom-dtrace-instrumentation/page.mdx
@@ -1,14 +1,19 @@
import { ArticleLayout } from '@/components/ArticleLayout'
-export const meta = {
+export const dynamic = 'force-static'
+export const article = {
author: 'Jip J. Dekker',
date: '2017-02-24',
title: 'Implementing custom DTrace instrumentation',
description:
'DTrace (and SystemTap) are often the “go to” when adding tracing in high performance environments such as for example operating systems. This note discusses adding instrumentation to your own application, so you can take advantage of these powerful tools.',
}
+export const metadata = {
+ title: article.title,
+ description: article.description,
+}
-export default (props) =>
+export default (props) =>
Last semester I had a chance to work with DTrace. In particular, I implemented
custom DTrace instrumentation in Encore and [Pony](http://www.ponylang.org/).
diff --git a/src/pages/articles/2022-08-homebrew-minizinc.mdx b/src/app/articles/2022-08-homebrew-minizinc/page.mdx
similarity index 94%
rename from src/pages/articles/2022-08-homebrew-minizinc.mdx
rename to src/app/articles/2022-08-homebrew-minizinc/page.mdx
index a648eab..143eb44 100644
--- a/src/pages/articles/2022-08-homebrew-minizinc.mdx
+++ b/src/app/articles/2022-08-homebrew-minizinc/page.mdx
@@ -1,14 +1,19 @@
import { ArticleLayout } from '@/components/ArticleLayout'
-export const meta = {
+export const dynamic = 'force-static'
+export const article = {
author: 'Jip J. Dekker',
date: '2022-08-15',
title: 'A Homebrew Tap for MiniZinc Solvers',
description:
"I'm proposing a Homebrew tap to make it easier for users to install different MiniZinc solvers. The tap already contains many of the open source solvers that are current contenders in the MiniZinc challenge, and I'm hoping to add any others that fit the infrastructure.",
}
+export const metadata = {
+ title: article.title,
+ description: article.description,
+}
-export default (props) =>
+export default (props) =>
TLDR; I'm proposing a [Homebrew](https://brew.sh/) tap to make it easier for users to install different MiniZinc solvers.
The tap already contains many of the open source solvers that are current contenders in the MiniZinc challenge, and I'm hoping to add any others that fit the infrastructure.
diff --git a/src/app/articles/page.tsx b/src/app/articles/page.tsx
new file mode 100644
index 0000000..3b79d5d
--- /dev/null
+++ b/src/app/articles/page.tsx
@@ -0,0 +1,60 @@
+import { type Metadata } from 'next'
+
+import { Card } from '@/components/Card'
+import { SimpleLayout } from '@/components/SimpleLayout'
+import { type ArticleWithSlug, getAllArticles } from '@/lib/articles'
+import { formatDate } from '@/lib/formatDate'
+
+function Article({ article }: { article: ArticleWithSlug }) {
+ return (
+
+
+
+ {article.title}
+
+
+ {formatDate(article.date)}
+
+ {article.description}
+ Read article
+
+
+ {formatDate(article.date)}
+
+
+ )
+}
+
+export const metadata: Metadata = {
+ title: 'Articles',
+ description:
+ 'All of my long-form thoughts on programming, leadership, product design, and more, collected in chronological order.',
+}
+
+export default async function ArticlesIndex() {
+ let articles = await getAllArticles()
+
+ return (
+
+
+
+ {articles.map((article) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/public/favicon.ico b/src/app/favicon.ico
similarity index 100%
rename from public/favicon.ico
rename to src/app/favicon.ico
diff --git a/src/app/feed.xml/route.ts b/src/app/feed.xml/route.ts
new file mode 100644
index 0000000..d8a9a3a
--- /dev/null
+++ b/src/app/feed.xml/route.ts
@@ -0,0 +1,62 @@
+import { getAllArticles } from '@/lib/articles'
+import assert from 'assert'
+import { Feed } from 'feed'
+
+export async function GET(req: Request) {
+ let siteUrl = process.env.NEXT_PUBLIC_SITE_URL
+
+ if (!siteUrl) {
+ throw Error('Missing NEXT_PUBLIC_SITE_URL environment variable')
+ }
+
+ let author = {
+ name: 'Jip J. Dekker',
+ email: 'jip.dekker@monash.edu',
+ }
+
+ let feed = new Feed({
+ title: author.name,
+ description:
+ 'The collection of writing by Jip about optimization, programming language, and general computer science',
+ author,
+ id: siteUrl,
+ link: siteUrl,
+ image: `${siteUrl}/favicon.ico`,
+ favicon: `${siteUrl}/favicon.ico`,
+ copyright: `All rights reserved ${new Date().getFullYear()}`,
+ feedLinks: {
+ rss2: `${siteUrl}/feed.xml`,
+ },
+ })
+
+ let articles = await getAllArticles()
+
+ for (let article of articles) {
+ let publicUrl = `${siteUrl}/articles/${article.slug}`
+ let title = article.title
+ let date = article.date
+ let description = article.description
+
+ assert(typeof title === 'string')
+ assert(typeof date === 'string')
+ assert(typeof description === 'string')
+
+ feed.addItem({
+ title,
+ id: publicUrl,
+ link: publicUrl,
+ description,
+ author: [author],
+ contributor: [author],
+ date: new Date(date),
+ })
+ }
+
+ return new Response(feed.rss2(), {
+ status: 200,
+ headers: {
+ 'content-type': 'application/xml',
+ 'cache-control': 's-maxage=31556952',
+ },
+ })
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..6dbd166
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,38 @@
+import { type Metadata } from 'next'
+
+import { Providers } from '@/app/providers'
+import { Layout } from '@/components/Layout'
+
+import '@/styles/tailwind.css'
+
+export const metadata: Metadata = {
+ title: {
+ template: '%s - Jip J. Dekker',
+ default: 'Jip J. Dekker - Optimisation Expert & Computer Scientist',
+ },
+ description:
+ 'I’m Jip, a researcher at the OPTIMA ARC research centre and Monash University, where we aim to make complex decisions easier through decision support and data insights. We design state-of-the-art optimization techniques, such as the MiniZinc modelling language, ready to be used in industry.',
+ alternates: {
+ types: {
+ 'application/rss+xml': `${process.env.NEXT_PUBLIC_SITE_URL}/feed.xml`,
+ },
+ },
+}
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 0000000..4067f6f
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,23 @@
+import { Button } from '@/components/Button'
+import { Container } from '@/components/Container'
+
+export default function NotFound() {
+ return (
+
+
+
+ 404
+
+
+ Page not found
+
+
+ Sorry, we couldn’t find the page you’re looking for.
+
+
+
+
+ )
+}
diff --git a/src/pages/index.jsx b/src/app/page.tsx
similarity index 65%
rename from src/pages/index.jsx
rename to src/app/page.tsx
index 86f3525..e004343 100644
--- a/src/pages/index.jsx
+++ b/src/app/page.tsx
@@ -1,12 +1,10 @@
-import Head from 'next/head'
-import Image from 'next/image'
+import Image, { type ImageProps } from 'next/image'
import Link from 'next/link'
import clsx from 'clsx'
import { Card } from '@/components/Card'
import { Container } from '@/components/Container'
import {
- ArrowDownIcon,
BriefcaseIcon,
GitHubIcon,
LinkedInIcon,
@@ -23,12 +21,10 @@ import image2 from '@/images/photos/image-4.jpg'
import image3 from '@/images/photos/image-2.jpg'
import image4 from '@/images/photos/image-3.jpg'
import image5 from '@/images/photos/image-5.jpg'
+import { type ArticleWithSlug, getAllArticles } from '@/lib/articles'
import { formatDate } from '@/lib/formatDate'
-import { generateRssFeed } from '@/lib/generateRssFeed'
-import { getAllArticles } from '@/lib/getAllArticles'
-import { Button } from '@/components/Button'
-function Article({ article }) {
+function Article({ article }: { article: ArticleWithSlug }) {
return (
@@ -43,7 +39,12 @@ function Article({ article }) {
)
}
-function SocialLink({ icon: Icon, ...props }) {
+function SocialLink({
+ icon: Icon,
+ ...props
+}: React.ComponentPropsWithoutRef & {
+ icon: React.ComponentType<{ className?: string }>
+}) {
return (
@@ -51,8 +52,53 @@ function SocialLink({ icon: Icon, ...props }) {
)
}
+interface Role {
+ company: string
+ title: string
+ logo: ImageProps['src']
+ start: string | { label: string; dateTime: string }
+ end: string | { label: string; dateTime: string }
+}
+
+function Role({ role }: { role: Role }) {
+ let startLabel =
+ typeof role.start === 'string' ? role.start : role.start.label
+ let startDate =
+ typeof role.start === 'string' ? role.start : role.start.dateTime
+
+ let endLabel = typeof role.end === 'string' ? role.end : role.end.label
+ let endDate = typeof role.end === 'string' ? role.end : role.end.dateTime
+
+ return (
+
+
+
+
+
+
Company
+
+ {role.company}
+
+
Role
+
+ {role.title}
+
+
Date
+
+ {' '}
+ —{' '}
+
+
+
+
+ )
+}
+
function Resume() {
- let resume = [
+ let resume: Array = [
{
company: 'OPTIMA & Monash University',
title: 'Research Fellow',
@@ -60,7 +106,7 @@ function Resume() {
start: '2021',
end: {
label: 'Present',
- dateTime: new Date().getFullYear(),
+ dateTime: new Date().getFullYear().toString(),
},
},
{
@@ -94,53 +140,14 @@ function Resume() {
{resume.map((role, roleIndex) => (
-
-
-
-
-
-
Company
-
- {role.company}
-
-
Role
-
- {role.title}
-
-
Date
-
- {' '}
- —{' '}
-
-
-
-
+
))}
-
-
-
{/*
+
*/}
)
@@ -173,18 +180,11 @@ function Photos() {
)
}
-export default function Home({ articles }) {
+export default async function Home() {
+ let articles = (await getAllArticles()).slice(0, 4)
+
return (
<>
-
-
- Jip J. Dekker - Optimisation Expert & Programming Language Designer
-
-
-
@@ -242,17 +242,3 @@ export default function Home({ articles }) {
>
)
}
-
-export async function getStaticProps() {
- if (process.env.NODE_ENV === 'production') {
- await generateRssFeed()
- }
-
- return {
- props: {
- articles: (await getAllArticles())
- .slice(0, 4)
- .map(({ component, ...meta }) => meta),
- },
- }
-}
diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx
new file mode 100644
index 0000000..8ceaf60
--- /dev/null
+++ b/src/app/projects/page.tsx
@@ -0,0 +1,93 @@
+import { type Metadata } from 'next'
+import Image from 'next/image'
+
+import { Card } from '@/components/Card'
+import { SimpleLayout } from '@/components/SimpleLayout'
+import { LinkIcon } from '@/components/SVGIcons'
+import logoChuffed from '@/images/logos/chuffed.png'
+import logoMiniZinc from '@/images/logos/minizinc.svg'
+import logoMZNPy from '@/images/logos/minizinc-python.svg'
+import logoPindakaas from '@/images/logos/pindakaas.svg'
+import logoShackle from '@/images/logos/shackle.svg'
+
+const projects = [
+ {
+ name: 'MiniZinc',
+ description:
+ 'A constraint modelling language for almost all types of optimization solvers.',
+ link: { href: 'http://www.minizinc.org', label: 'minizinc.org' },
+ logo: logoMiniZinc,
+ },
+ {
+ name: 'Chuffed',
+ description: 'The solver that brought Lazy Clause Generation to the world.',
+ link: { href: 'https://github.com/chuffed/chuffed', label: 'github.com' },
+ logo: logoChuffed,
+ },
+ {
+ name: 'Shackle',
+ description: 'The next generation of constraint model rewriting tooling.',
+ link: {
+ href: 'https://github.com/shackle-rs/shackle',
+ label: 'github.com',
+ },
+ logo: logoShackle,
+ },
+ {
+ name: 'Pindakaas',
+ description:
+ 'A library that helps you create state-of-the-art encodings for Boolean satisfiability solvers.',
+ link: { href: '#', label: 'TBA' },
+ logo: logoPindakaas,
+ },
+ {
+ name: 'MiniZinc Python',
+ description:
+ 'Easily run MiniZinc from Python, with incremental solving and direct data access.',
+ link: {
+ href: 'https://github.com/MiniZinc/minizinc-python',
+ label: 'github.com',
+ },
+ logo: logoMZNPy,
+ },
+]
+
+export const metadata: Metadata = {
+ title: 'Projects',
+ description: 'From my brain to your computer',
+}
+
+export default function Projects() {
+ return (
+
+
+ {projects.map((project) => (
+
+
+
+
+
+ {project.name}
+
+ {project.description}
+
+
+ {project.link.label}
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/app/providers.tsx b/src/app/providers.tsx
new file mode 100644
index 0000000..028e29d
--- /dev/null
+++ b/src/app/providers.tsx
@@ -0,0 +1,30 @@
+'use client'
+
+import { createContext, useEffect, useRef } from 'react'
+import { usePathname } from 'next/navigation'
+import { ThemeProvider, useTheme } from 'next-themes'
+
+function usePrevious(value: T) {
+ let ref = useRef()
+
+ useEffect(() => {
+ ref.current = value
+ }, [value])
+
+ return ref.current
+}
+
+export const AppContext = createContext<{ previousPathname?: string }>({})
+
+export function Providers({ children }: { children: React.ReactNode }) {
+ let pathname = usePathname()
+ let previousPathname = usePrevious(pathname)
+
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/src/app/speaking/page.tsx b/src/app/speaking/page.tsx
new file mode 100644
index 0000000..dbaf66e
--- /dev/null
+++ b/src/app/speaking/page.tsx
@@ -0,0 +1,98 @@
+import { type Metadata } from 'next'
+
+import { Card } from '@/components/Card'
+import { Section } from '@/components/Section'
+import { SimpleLayout } from '@/components/SimpleLayout'
+
+function SpeakingSection({
+ children,
+ ...props
+}: React.ComponentPropsWithoutRef) {
+ return (
+
+
{children}
+
+ )
+}
+
+function Appearance({
+ title,
+ description,
+ event,
+ cta,
+ href,
+}: {
+ title: string
+ description: string
+ event: string
+ cta: string
+ href: string
+}) {
+ return (
+
+
+ {title}
+
+ {event}
+ {description}
+ {cta}
+
+ )
+}
+
+export const metadata: Metadata = {
+ title: 'Speaking',
+ description:
+ 'I’ve spoken at events all around the world and been interviewed for many podcasts.',
+}
+
+export default function Speaking() {
+ return (
+
+
+
+
+ I was using an Intel-based 16” MacBook Pro prior to this and the
+ difference is night and day. I’ve never heard the fans turn on a
+ single time, even under the incredibly heavy loads I put it through
+ with our various launch simulations.
+
+
+ The only display on the market if you want something HiDPI and
+ bigger than 27”. When you’re working at planetary scale, every pixel
+ you can get counts.
+
+
+ They don’t make keyboards the way they used to. I buy these any time
+ I see them go up for sale and keep them in storage in case I need
+ parts or need to retire my main.
+
+
+ Something about all the gestures makes me feel like a wizard with
+ special powers. I really like feeling like a wizard with special
+ powers.
+
+
+ If I’m going to slouch in the worst ergonomic position imaginable
+ all day, I might as well do it in an expensive chair.
+
+
+
+
+ I don’t care if it’s missing all of the fancy IDE features everyone
+ else relies on, Sublime Text is still the best text editor ever
+ made.
+
+
+ I’m honestly not even sure what features I get with this that aren’t
+ just part of the macOS Terminal but it’s what I use.
+
+
+ Great software for working with databases. Has saved me from
+ building about a thousand admin interfaces for my various projects
+ over the years.
+
+
+
+
+ We started using Figma as just a design tool but now it’s become our
+ virtual whiteboard for the entire company. Never would have expected
+ the collaboration features to be the real hook.
+
+
+
+
+ It’s not the newest kid on the block but it’s still the fastest. The
+ Sublime Text of the application launcher world.
+
+
+ Using a daily notes system instead of trying to keep things
+ organized by topics has been super powerful for me. And with
+ Reflect, it’s still easy for me to keep all of that stuff
+ discoverable by topic even though all of my writing happens in the
+ daily note.
+
+
+ Great tool for scheduling meetings while protecting my calendar and
+ making sure I still have lots of time for deep work during the week.
+
+
+ Simple tool for blocking distracting websites when I need to just do
+ the work and get some momentum going.
+
+
+
+
+ )
+}
diff --git a/src/components/ArticleLayout.jsx b/src/components/ArticleLayout.jsx
deleted file mode 100644
index a6dc640..0000000
--- a/src/components/ArticleLayout.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import Head from 'next/head'
-import { useRouter } from 'next/router'
-
-import { Container } from '@/components/Container'
-import { Prose } from '@/components/Prose'
-import { formatDate } from '@/lib/formatDate'
-import { ArrowLeftIcon } from '@/components/SVGIcons'
-
-export function ArticleLayout({
- children,
- meta,
- isRssFeed = false,
- previousPathname,
-}) {
- let router = useRouter()
-
- if (isRssFeed) {
- return children
- }
-
- return (
- <>
-
- {`${meta.title} - Jip J. Dekker`}
-
-
-
-
-
- {previousPathname && (
-
- )}
-
-
-
- {meta.title}
-
-
-
- {children}
-
-
-
-
- >
- )
-}
diff --git a/src/components/ArticleLayout.tsx b/src/components/ArticleLayout.tsx
new file mode 100644
index 0000000..5a62546
--- /dev/null
+++ b/src/components/ArticleLayout.tsx
@@ -0,0 +1,58 @@
+'use client'
+
+import { useContext } from 'react'
+import { useRouter } from 'next/navigation'
+
+import { AppContext } from '@/app/providers'
+import { Container } from '@/components/Container'
+import { Prose } from '@/components/Prose'
+import { type ArticleWithSlug } from '@/lib/articles'
+import { formatDate } from '@/lib/formatDate'
+import { ArrowLeftIcon } from './SVGIcons'
+
+export function ArticleLayout({
+ article,
+ children,
+}: {
+ article: ArticleWithSlug
+ children: React.ReactNode
+}) {
+ let router = useRouter()
+ let { previousPathname } = useContext(AppContext)
+
+ return (
+
+