Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions apps/roadmap/app/api/md/[...slug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
renderLaneMarkdown,
renderPersonMarkdown,
renderTicketMarkdown,
renderHomeMarkdown,
renderAboutMarkdown,
renderExperimentsMarkdown,
} from "@/lib/markdown"
import { EXPERIMENTS } from "@/lib/experiments"

const HEADERS = {
"Content-Type": "text/markdown; charset=utf-8",
Expand Down Expand Up @@ -75,6 +79,25 @@ export async function GET(
})
}

case "index": {
const features = getAllFeatures()
return new Response(renderHomeMarkdown(features, EXPERIMENTS), {
headers: HEADERS,
})
}

case "about": {
return new Response(renderAboutMarkdown(EXPERIMENTS), {
headers: HEADERS,
})
}

case "experiments": {
return new Response(renderExperimentsMarkdown(EXPERIMENTS), {
headers: HEADERS,
})
}

default:
return notFound(`Unknown path: /${slug.join("/")}`)
}
Expand Down
5 changes: 4 additions & 1 deletion apps/roadmap/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ export async function GET() {
"",
`This site provides a read-only view of the JesusFilm project roadmap. Every page is available as markdown by appending \`.md\` to the URL.`,
"",
`## Overview`,
`## Pages`,
"",
`- [Home](/index.md): Overview with progress stats, recently shipped features, and live experiments`,
`- [Full Roadmap](/roadmap.md): All features with status, priority, owner, and timeline`,
`- [About](/about.md): Mission, team, focus areas, guardrails, and timeline`,
`- [Experiments](/experiments.md): Active project demos with links and team info`,
"",
`## Lanes`,
"",
Expand Down
183 changes: 183 additions & 0 deletions apps/roadmap/lib/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Feature, FeatureStatus, Lane } from "./features"
import { getStatusCounts, getLaneLabel } from "./features"
import type { Experiment } from "./experiments"

function statusEmoji(status: FeatureStatus): string {
switch (status) {
Expand Down Expand Up @@ -106,3 +107,185 @@ export function renderTicketMarkdown(feature: Feature): string {
]
return lines.join("\n")
}

export function renderHomeMarkdown(
features: Feature[],
experiments: Experiment[],
): string {
const counts = getStatusCounts(features)

const recentlyCompleted = features
.filter((f) => f.status === "complete" && f.start_date)
.sort((a, b) => {
const endA = new Date(
new Date(a.start_date + "T00:00:00").getTime() +
(a.duration - 1) * 86400000,
)
const endB = new Date(
new Date(b.start_date + "T00:00:00").getTime() +
(b.duration - 1) * 86400000,
)
return endB.getTime() - endA.getTime()
})
.slice(0, 5)

const lines: string[] = [
`# JesusFilm Digital Strategies Roadmap`,
"",
`> Reaching every person, in every language, through the power of AI.`,
"",
`The Digital Strategies Department is building trusted AI capabilities that help people discover gospel content, engage meaningfully with Scripture, and take faithful next steps.`,
"",
`## Progress at a Glance`,
"",
`- **Features Shipped:** ${counts.complete}`,
`- **In Progress:** ${counts["in-progress"]}`,
`- **Total Planned:** ${features.length}`,
"",
`[Full Roadmap](/roadmap.md) | [About](/about.md) | [Experiments](/experiments.md)`,
]

if (recentlyCompleted.length > 0) {
lines.push("", `## Recently Shipped`, "")
for (const f of recentlyCompleted) {
lines.push(`- [${f.title}](/ticket/${f.id}.md) — ${getLaneLabel(f.lane)}`)
}
}

if (experiments.length > 0) {
lines.push("", `## Live Experiments`, "")
for (const exp of experiments) {
const link =
exp.links[0] && !exp.comingSoon
? ` — [${exp.links[0].label}](${exp.links[0].href})`
: exp.comingSoon
? " — Coming soon"
: ""
lines.push(`- **${exp.title}:** ${exp.description}${link}`)
}
}

return lines.join("\n")
}

export function renderAboutMarkdown(experiments: Experiment[]): string {
const team = [
{ name: "Tataihono", role: "Architect" },
{ name: "Vlad", role: "Product Owner & Manager Builder" },
{ name: "Ekkasit", role: "AI Experience Generation" },
{ name: "Nisal", role: "Backend" },
{ name: "Urim", role: "Frontend (Web & Mobile)" },
]

const principles = [
{
title: "Theological fidelity",
description:
"AI assists, humans verify. No speculative doctrine. Every generated piece of content is grounded in trusted Scripture and reviewed before publication.",
},
{
title: "Human oversight",
description:
"Generated content starts as drafts, published only after human review. The AI proposes; the ministry team decides.",
},
{
title: "Safe experimentation",
description:
"Practical outcomes over full automation. We ship incremental value, measure impact, and iterate with care.",
},
{
title: "Ministry first",
description:
"Technology serves the mission, not the other way around. Every capability we build is measured by lives reached, not models deployed.",
},
]

const quarters = [
{ label: "Sept – Nov 2025", title: "Foundation" },
{ label: "Dec 2025 – Feb 2026", title: "Infrastructure & Data" },
{ label: "March – May 2026", title: "Search, Topics, Audio (current)" },
{
label: "June – Aug 2026",
title: "Personalization, Publishing, Video AI",
},
]

const lines: string[] = [
`# About — JesusFilm Digital Strategies`,
"",
`> Reaching every person, in every language, through the power of AI.`,
"",
`Build trusted, scalable AI capabilities that help people discover gospel content, engage meaningfully with Scripture, and take faithful next steps, while maintaining strong theological and ministry guardrails.`,
"",
`## The Opportunity`,
"",
`Billions of people across hundreds of languages are searching for hope, meaning, and truth. The Jesus Film Project has decades of gospel media (films, short videos, Scripture resources) but connecting the right content to the right person at the right moment remains an enormous challenge. AI changes the equation — not by replacing human ministry, but by making it possible to structure, discover, and deliver content at a scale that was previously impossible.`,
"",
`## Three Focus Areas`,
"",
`1. **Content Discovery & Recommendation** — Structure and tag media so people and AI systems can discover related content. Semantic search, embeddings, and intelligent recommendations.`,
`2. **Topic Pages & Guided Journeys** — Use clustered content and AI assistance to generate clear, public-facing topic pages. Tens of thousands of pages, each a doorway to the gospel.`,
`3. **AI-Assisted Media Creation** — Reduce the cost and effort of creating media through AI-assisted subtitles, audio, and video. Break language barriers at scale.`,
"",
`## Year 1 Timeline`,
"",
...quarters.map((q) => `- **${q.label}:** ${q.title}`),
"",
`## The Team`,
"",
`| Name | Role |`,
`|------|------|`,
...team.map((m) => `| ${m.name} | ${m.role} |`),
"",
`## Our Guardrails`,
"",
...principles.map((p) => `- **${p.title}:** ${p.description}`),
]

if (experiments.length > 0) {
lines.push("", `## Live Experiments`, "")
for (const exp of experiments) {
const link =
exp.links[0] && !exp.comingSoon
? ` — [${exp.links[0].label}](${exp.links[0].href})`
: exp.comingSoon
? " — Coming soon"
: ""
lines.push(`- **${exp.title}:** ${exp.description}${link}`)
}
}

return lines.join("\n")
}

export function renderExperimentsMarkdown(experiments: Experiment[]): string {
const lines: string[] = [
`# Experiments`,
"",
`Active projects demonstrating what we're building. Each explores a different way to use technology for ministry, from hand-crafted experiences to AI-generated content to mobile delivery.`,
]

for (const exp of experiments) {
lines.push(
"",
`## ${exp.number}. ${exp.title}`,
"",
exp.description,
"",
`- **Team:** ${exp.team.map((k) => k.charAt(0).toUpperCase() + k.slice(1)).join(", ")}`,
)

if (exp.comingSoon) {
lines.push(`- **Status:** Coming soon`)
} else if (exp.links.length > 0) {
for (const link of exp.links) {
lines.push(`- **${link.label}:** ${link.href}`)
}
if (exp.loginRequired) {
lines.push(`- *Login required*`)
}
}
}

return lines.join("\n")
}
4 changes: 4 additions & 0 deletions apps/roadmap/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import type { NextConfig } from "next"
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: "/index.md",
destination: "/api/md/index",
},
{
source: "/:path*.md",
destination: "/api/md/:path*",
Expand Down
Loading