-
Notifications
You must be signed in to change notification settings - Fork 0
Add Blog #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Blog #9
Changes from 8 commits
db54376
bddb9be
16b9d44
cd8fe4e
d4946d1
7261d10
fa927aa
23a86cd
2aa5231
6c104ff
3868997
adb0a87
7fd3d6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| --- | ||
| title: "Building services that endure" | ||
| description: "A practical way to make early delivery, policy learning, and long-term stewardship reinforce one another." | ||
| pubDate: 2026-06-18 | ||
| author: Bryan Bassett | ||
| tags: | ||
| - delivery | ||
| - public services | ||
| draft: true | ||
| --- | ||
|
|
||
| import { Image } from "astro:assets"; | ||
| import serviceMap from "./service-map.png"; | ||
|
|
||
| The first release of a public service is not the finish line. It is the first | ||
| reliable opportunity to learn how policy, operations, and technology behave | ||
| together in the real world. | ||
|
|
||
| <Image | ||
| src={serviceMap} | ||
| alt="Focus team members collaborating around a table" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume we're not going to publish this one, but I think the alt is wrong?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, we're not going to publish this, but I'll update the alt text here. |
||
| widths={[640, 960, 1280]} | ||
| sizes="(max-width: 720px) 100vw, 720px" | ||
| /> | ||
|
|
||
| ## Start with a complete path | ||
|
|
||
| A small release should still let someone complete a meaningful task. That may | ||
| mean serving one program, one user group, or one carefully chosen scenario | ||
| before broadening the service. The boundary is narrow; the outcome is whole. | ||
|
|
||
| This approach gives a team evidence that planning alone cannot provide: | ||
|
|
||
| - where people hesitate or abandon the process; | ||
| - which policy questions need an operational answer; | ||
| - what staff need in order to support the service; and | ||
| - which technical choices will be expensive to reverse. | ||
|
|
||
| ## Treat learning as delivery | ||
|
|
||
| The goal of an early release is not simply to ship sooner. It is to shorten the | ||
| distance between a decision and the evidence needed to improve it. Teams can | ||
| then make smaller, clearer decisions while change is still affordable. | ||
|
|
||
| Enduring services grow from that rhythm: deliver a useful slice, observe the | ||
| whole system, and make the next commitment with better information. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <div class="mb-4 text-sm font-semibold uppercase text-[var(--color-brand-accent)]"> | ||
| <slot /> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { defineCollection, reference } from "astro:content"; | ||
| import { glob } from "astro/loaders"; | ||
| import { z } from "astro/zod"; | ||
|
|
||
| const blog = defineCollection({ | ||
| loader: glob({ | ||
| base: "./src/blog", | ||
| pattern: "**/index.mdx", | ||
| generateId: ({ entry }) => entry.replace(/\/index\.mdx$/, ""), | ||
| }), | ||
| schema: z.object({ | ||
| title: z.string().min(1), | ||
| description: z.string().min(1), | ||
| pubDate: z.coerce.date(), | ||
| author: z.string().min(1), | ||
| tags: z.array(z.string().min(1)), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks unused? |
||
| draft: z.coerce.boolean().default(false), | ||
| }), | ||
| }); | ||
|
|
||
| export const collections = { blog }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { getCollection } from "astro:content"; | ||
|
|
||
| export async function getBlogPostsSortedByPubDate() { | ||
| return (await getCollection('blog')).filter( | ||
| (x) => !x.data.draft, | ||
| ).sort( | ||
| (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), | ||
| ); | ||
| } | ||
|
|
||
| export function formatPostDate(date: Date) { | ||
| return new Intl.DateTimeFormat("en-US", { | ||
| dateStyle: "long", | ||
| timeZone: "UTC", | ||
| }).format(date); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| --- | ||
| import { render } from "astro:content"; | ||
| import Eyebrow from "../../components/shared/Eyebrow.astro"; | ||
| import Layout from "../../layouts/Layout.astro"; | ||
| import { | ||
| formatPostDate, | ||
| getBlogPostsSortedByPubDate, | ||
| } from "../../lib/blog"; | ||
|
|
||
| export async function getStaticPaths() { | ||
| return (await getBlogPostsSortedByPubDate()).map((post) => ({ | ||
| params: { slug: post.id }, | ||
| props: { post }, | ||
| })); | ||
| } | ||
|
|
||
| const { post } = Astro.props; | ||
| const { Content } = await render(post); | ||
| --- | ||
|
|
||
| <Layout | ||
| title={post.data.title} | ||
| description={post.data.description} | ||
| ogType="article" | ||
| > | ||
| <article class="page-shell pb-20 pt-20 sm:pb-28"> | ||
| <header class="layout-grid border-b border-[var(--color-brand-border)] pb-6"> | ||
| <div class="col-span-12"> | ||
| <Eyebrow><a href="/blog">Blog</a></Eyebrow> | ||
| <h1 class="display-2 mt-5">{post.data.title}</h1> | ||
| </div> | ||
| <div class="col-span-12 lg:col-span-8"> | ||
| <p class="body-copy text-[1.2rem]">{post.data.description}</p> | ||
| <div class="mt-6 flex flex-wrap gap-x-3 text-[0.95rem] text-[var(--color-brand-soft)]"> | ||
| <time dateTime={post.data.pubDate}>{formatPostDate(post.data.pubDate)}</time> | ||
| <span aria-hidden="true">•</span> | ||
| <span>{post.data.author}</span> | ||
| </div> | ||
| </div> | ||
| </header> | ||
|
|
||
| <div class="layout-grid pt-10"> | ||
| <div class="blog-prose col-span-12 lg:col-span-8"> | ||
| <Content /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <footer class="layout-grid mt-14 border-t border-[var(--color-brand-border)] pt-8"> | ||
| <div class="col-span-12 lg:col-span-8"> | ||
| <a class="inline-flex font-medium text-[var(--color-brand-accent)] underline underline-offset-4" href="/blog/"> | ||
| Back to all blog posts | ||
| </a> | ||
| </div> | ||
| </footer> | ||
| </article> | ||
| </Layout> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| --- | ||
| import Layout from "../../layouts/Layout.astro"; | ||
| import Eyebrow from "../../components/shared/Eyebrow.astro"; | ||
| import { | ||
| formatPostDate, | ||
| getBlogPostsSortedByPubDate, | ||
| } from "../../lib/blog"; | ||
|
|
||
| const posts = await getBlogPostsSortedByPubDate(); | ||
| --- | ||
|
|
||
| <Layout | ||
| title="Blog" | ||
| description="Ideas and field notes from Focus about public digital services, delivery, and building systems that endure." | ||
| > | ||
| <section class="page-shell pt-6 pb-12 sm:pt-20 sm:pb-18"> | ||
| <Eyebrow>Blog</Eyebrow> | ||
|
|
||
| <div> | ||
| <ol> | ||
| {posts.map((post) => ( | ||
| <li key={post.id} class="border-b border-[var(--color-brand-border)]"> | ||
| <article class="grid gap-7 py-10 md:grid-cols-[minmax(0,1fr)_13rem] md:items-start md:py-12"> | ||
| <div> | ||
| <h2 class="section-heading"> | ||
| <a | ||
| class="no-underline hover:text-[var(--color-brand-accent)]" | ||
| href={`/blog/${post.id}/`} | ||
| > | ||
| {post.data.title} | ||
| </a> | ||
| </h2> | ||
| <p class="body-copy mt-4">{post.data.description}</p> | ||
| <div class="mt-4 flex flex-wrap gap-x-3 text-[0.86rem] text-[var(--color-brand-soft)]"> | ||
| <time dateTime={post.data.pubDate}>{formatPostDate(post.data.pubDate)}</time> | ||
| <span aria-hidden="true">•</span> | ||
| <span>{post.data.author}</span> | ||
| </div> | ||
| </div> | ||
| </article> | ||
| </li> | ||
| ))} | ||
| </ol> | ||
| </div> | ||
|
|
||
| <a class="mt-8 inline-flex text-[0.92rem] font-medium text-[var(--color-brand-accent)] underline underline-offset-4" href="/rss.xml"> | ||
| Subscribe via RSS | ||
| </a> | ||
| </section> | ||
| </Layout> |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||
| import rss from "@astrojs/rss"; | ||||||
| import { getBlogPostsSortedByPubDate } from "../lib/blog"; | ||||||
|
|
||||||
| export async function GET(context: { site: URL }) { | ||||||
| const posts = await getBlogPostsSortedByPubDate(); | ||||||
|
|
||||||
| return rss({ | ||||||
| title: "Focus blog", | ||||||
| description: | ||||||
| "Ideas and field notes from Focus about public digital services, delivery, and building systems that endure.", | ||||||
| site: context.site, | ||||||
| items: posts.map((post) => ({ | ||||||
| title: post.data.title, | ||||||
| description: post.data.description, | ||||||
| pubDate: post.data.pubDate, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||
| link: `/blog/${post.id}/`, | ||||||
| categories: post.data.tags, | ||||||
| author: post.data.author, | ||||||
| })), | ||||||
| }); | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we pin these new deps?