From 38ec5d28461848526cd547ff798fcd0571366fff Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 16 Mar 2026 18:26:10 -0400 Subject: [PATCH] Add blog and redesign blog/roadmap pages - Add blog collection (content config, prose styles, introducing-fabro post) - Add Blog link to homepage nav and footer - Make Layout description prop dynamic for per-page meta/OG tags - Extract shared Nav, Footer, PageScripts components from duplicated markup - Blog index: compact header, featured card for latest post, row list for older posts - Blog post: clean reading surface (no grid/noise overlay), reading time, better header - Roadmap: improved card contrast with tinted backgrounds, conditional section rendering Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/marketing/src/components/Footer.astro | 34 +++ apps/marketing/src/components/Nav.astro | 66 +++++ .../src/components/PageScripts.astro | 27 ++ apps/marketing/src/content.config.ts | 12 +- .../src/content/blog/introducing-fabro.md | 22 ++ apps/marketing/src/layouts/Layout.astro | 9 +- apps/marketing/src/pages/blog/[slug].astro | 64 +++++ apps/marketing/src/pages/blog/index.astro | 82 ++++++ apps/marketing/src/pages/index.astro | 3 + apps/marketing/src/pages/roadmap.astro | 270 ++++++------------ apps/marketing/src/styles/global.css | 63 ++++ 11 files changed, 466 insertions(+), 186 deletions(-) create mode 100644 apps/marketing/src/components/Footer.astro create mode 100644 apps/marketing/src/components/Nav.astro create mode 100644 apps/marketing/src/components/PageScripts.astro create mode 100644 apps/marketing/src/content/blog/introducing-fabro.md create mode 100644 apps/marketing/src/pages/blog/[slug].astro create mode 100644 apps/marketing/src/pages/blog/index.astro diff --git a/apps/marketing/src/components/Footer.astro b/apps/marketing/src/components/Footer.astro new file mode 100644 index 000000000..24bbc08aa --- /dev/null +++ b/apps/marketing/src/components/Footer.astro @@ -0,0 +1,34 @@ + diff --git a/apps/marketing/src/components/Nav.astro b/apps/marketing/src/components/Nav.astro new file mode 100644 index 000000000..34b5a5717 --- /dev/null +++ b/apps/marketing/src/components/Nav.astro @@ -0,0 +1,66 @@ +--- +interface Props { + currentPage?: "blog" | "roadmap"; +} + +const { currentPage } = Astro.props; + +const linkClass = (page?: string) => + page === currentPage + ? "text-ice-50" + : "text-ice-100 hover:text-ice-50"; +--- + + diff --git a/apps/marketing/src/components/PageScripts.astro b/apps/marketing/src/components/PageScripts.astro new file mode 100644 index 000000000..360dec312 --- /dev/null +++ b/apps/marketing/src/components/PageScripts.astro @@ -0,0 +1,27 @@ + diff --git a/apps/marketing/src/content.config.ts b/apps/marketing/src/content.config.ts index c16e06018..8cae23293 100644 --- a/apps/marketing/src/content.config.ts +++ b/apps/marketing/src/content.config.ts @@ -12,4 +12,14 @@ const roadmap = defineCollection({ }), }); -export const collections = { roadmap }; +const blog = defineCollection({ + loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }), + schema: z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + author: z.string(), + }), +}); + +export const collections = { roadmap, blog }; diff --git a/apps/marketing/src/content/blog/introducing-fabro.md b/apps/marketing/src/content/blog/introducing-fabro.md new file mode 100644 index 000000000..819f118fc --- /dev/null +++ b/apps/marketing/src/content/blog/introducing-fabro.md @@ -0,0 +1,22 @@ +--- +title: "Introducing Fabro" +description: "Fabro is an open-source workflow orchestration platform that lets expert engineers define their process as a graph and let AI agents execute it." +date: 2026-03-16 +author: "Fabro Team" +--- + +Most AI coding tools give you a chat window and hope for the best. Fabro takes a different approach: you define your engineering process as a workflow graph, and Fabro executes it — with verification gates, human checkpoints, and full observability at every step. + +## Why workflows? + +A chat-based agent is a single loop: prompt, act, repeat. That works for small tasks, but it falls apart when you need structure — when the plan should be approved before implementation begins, when tests must pass before the PR is opened, when a second model should cross-review the first. + +Fabro workflows are DOT graphs. Each node is a stage with a specific role: planning, coding, reviewing, testing. Edges define the flow. Human-in-the-loop gates let you intervene where it matters and step back where it doesn't. + +## Multi-model by design + +Not every stage needs a frontier model. Fabro's CSS-like model stylesheets let you assign models by stage ID, class, or shape — so you can use fast models for triage and frontier models for the work that matters. Swap providers without changing your workflow. + +## What's next + +We're just getting started. Check the [roadmap](/roadmap) to see what we're building, and join us on [Discord](/discord) to shape what comes next. diff --git a/apps/marketing/src/layouts/Layout.astro b/apps/marketing/src/layouts/Layout.astro index b21ea7aba..1f8581c37 100644 --- a/apps/marketing/src/layouts/Layout.astro +++ b/apps/marketing/src/layouts/Layout.astro @@ -3,9 +3,10 @@ import "../styles/global.css"; interface Props { title: string; + description?: string; } -const { title } = Astro.props; +const { title, description = "Fabro is the open source dark software factory for expert engineers. Define your process as a workflow graph, let AI agents execute it, and intervene only where it matters." } = Astro.props; --- @@ -13,7 +14,7 @@ const { title } = Astro.props; - + @@ -21,7 +22,7 @@ const { title } = Astro.props; - + @@ -31,7 +32,7 @@ const { title } = Astro.props; - + diff --git a/apps/marketing/src/pages/blog/[slug].astro b/apps/marketing/src/pages/blog/[slug].astro new file mode 100644 index 000000000..4cd03cf02 --- /dev/null +++ b/apps/marketing/src/pages/blog/[slug].astro @@ -0,0 +1,64 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import Nav from "../../components/Nav.astro"; +import Footer from "../../components/Footer.astro"; +import PageScripts from "../../components/PageScripts.astro"; +import { getCollection, render } from "astro:content"; + +export async function getStaticPaths() { + const posts = await getCollection("blog"); + return posts.map((post) => ({ + params: { slug: post.id }, + props: { post }, + })); +} + +const { post } = Astro.props; +const { Content } = await render(post); + +const formatDate = (date: Date) => + date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }); + +const wordCount = post.body?.split(/\s+/).length ?? 0; +const readingTime = Math.max(1, Math.round(wordCount / 250)); +--- + + +