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
10 changes: 7 additions & 3 deletions src/components/ContentCard.astro
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
---
import { renderInlineMarkdown } from "@lib/markdown";

type HeadingLevel = 2 | 3 | 4 | 5 | 6;

type Props = {
titlePrefix?: string;
title: string;
subtitle: string;
link: string;
ariaLabel?: string;
maxLines?: number | "none";
headingLevel?: HeadingLevel;
}

const { titlePrefix, title, subtitle, link, ariaLabel, maxLines = 2 } = Astro.props;
const { titlePrefix, title, subtitle, link, ariaLabel, maxLines = 2, headingLevel = 3 } = Astro.props;
const HeadingTag = `h${headingLevel}` as keyof HTMLElementTagNameMap;
const renderedTitlePrefix = titlePrefix ? renderInlineMarkdown(titlePrefix) : undefined;
const renderedTitle = renderInlineMarkdown(title);
const renderedSubtitle = renderInlineMarkdown(subtitle);
Expand Down Expand Up @@ -38,12 +42,12 @@ const accessibleLabel = ariaLabel || `${titlePrefix ? titlePrefix + ': ' : ''}${
aria-label={accessibleLabel}
class="relative group flex flex-nowrap py-3 px-4 pr-10 rounded-lg border border-black/15 dark:border-white/20 hover:bg-black/10 dark:hover:bg-white/10 hover:text-black dark:hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500 dark:focus-visible:ring-blue-400 motion-safe:transition-colors motion-safe:duration-300 motion-safe:ease-in-out">
<div class="flex flex-col flex-1">
<h3 class="font-semibold text-base">
<HeadingTag class="font-semibold text-base">
{renderedTitlePrefix && (
<span class="font-bold" set:html={renderedTitlePrefix + ":&nbsp;"}></span>
)}
<span class="font-semibold" set:html={renderedTitle}></span>
</h3>
</HeadingTag>
<p class={`text-sm ${lineClampClass}`} set:html={renderedSubtitle}></p>
</div>
<svg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Swift's syntax for trailing closures and multiple trailing closures are fantasti

Especially in such an otherwise-expressive language, this restriction is a bit jarring, and has real impacts on API design.

### Method Pairs
## Method Pairs

As a simple example, I think it's helpful to include method pairs like these:

Expand Down Expand Up @@ -50,7 +50,7 @@ var activeComponents: Set<ComponentID> {

Given the need to use labels to disambiguate between the two methods, we wind up with `@autoclosure` being the best fit for this API (instead of just using closures).

### Fused Functional Chains
## Fused Functional Chains

As another example, for performance reason I often create "fused" versions of common functional chains: a fused "map, filter", a fused "filter, map", and so on.

Expand Down Expand Up @@ -97,10 +97,10 @@ let premiumContactInfo = orders.mapFilterMap
// feels clunky no matter how you finesse the formatting
let premiumContactInfo = orders.mapFilterMap
{ $0.customer }
filter: { $0.isPremium }
filter: { $0.isPremium }
map: { $0.contactInfo }
```

### Is There Hope?
## Is There Hope?

Sadly, no: this capability has already been discussed-and-decided against, [as discussed a bit here](https://forums.swift.org/t/can-first-trailing-closure-be-named/69793/8).
3 changes: 0 additions & 3 deletions src/content/briefs/testing/decision-execution-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,4 @@ TODO: provide a *motivated*, *concrete* example.






The code in the "decision" phase should *generally* be structured as a pure function that receives all relevant information via parameters and returns a a function that returns a data item, e.g.:
26 changes: 13 additions & 13 deletions src/content/projects/agentic-navigation-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,19 @@ For the *initial* implementation, I used a specification-driven workflow:
2. In *plan mode*, I had Opus generate a high-level roadmap with distinct *phases* (and iterated a bit until it was satisfactory)
3. I asked Claude to implement "phase 1" (and just "phase 1")
4. I had Claude write a `ContinuingMission.md` file that:
- described the work done so far
- described the work remaining
- described the immediate "next steps" for the next session
4. I then entered a loop like this:
- start a fresh session
- have Claude copy the `ContinuingMission.md` file into a `missions/` folder in the repo (and rename with a timestamp, to make it unique)
- have Claude read the `ContinuingMission.md` file and take on the next task
- review the results, offer feedback, and keep Claude iterating until he finished the task
- have Claude *rewrite* `ContinuingMission.md` to once again:
- describe the work done so far
- describe the work remaining
- describe the immediate "next steps" for the next session
5. I kept repeating that loop until the initial pass on the project was complete
- described the work done so far
- described the work remaining
- described the immediate "next steps" for the next session
5. I then entered a loop like this:
- start a fresh session
- have Claude copy the `ContinuingMission.md` file into a `missions/` folder in the repo (and rename with a timestamp, to make it unique)
- have Claude read the `ContinuingMission.md` file and take on the next task
- review the results, offer feedback, and keep Claude iterating until he finished the task
- have Claude *rewrite* `ContinuingMission.md` to once again:
- describe the work done so far
- describe the work remaining
- describe the immediate "next steps" for the next session
6. I kept repeating that loop until the initial pass on the project was complete

Since this was my first pure vibe-coding experiment, I iteratively improved my workflow as I went:

Expand Down
2 changes: 1 addition & 1 deletion src/content/projects/hdxl-xctest-retrofit/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repoURL: "https://github.com/plx/hdxl-xctest-retrofit/"

1. migrate from `XCTestCase` subclasses to `@Suite` structs
2. apply `@Test` annotation to test functions[^2]
2. prepend `#` to `XCTAssert*` calls
3. prepend `#` to `XCTAssert*` calls

[^1]: The primary gaps are around expectations, expected failures, and attachments—IMHO those don't map cleanly to Swift Testing's APIs, so they're currently unsupported.

Expand Down
38 changes: 24 additions & 14 deletions src/lib/contentCardHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,66 @@
import type { CollectionEntry } from "astro:content";
import { extractCategoryFromSlug, getCategory } from "./category";

type HeadingLevel = 2 | 3 | 4 | 5 | 6;

type CardOptions = {
maxLines?: number | "none";
headingLevel?: HeadingLevel;
};

/**
* Transform a blog or project entry into ContentCard props
* Transform a blog entry into ContentCard props
*/
export function getBlogCardProps(entry: CollectionEntry<"blog">, maxLines?: number | "none") {
export function getBlogCardProps(entry: CollectionEntry<"blog">, options?: CardOptions) {
const displayTitle = entry.data.cardTitle || entry.data.title;

return {
title: displayTitle,
subtitle: entry.data.description,
link: `/${entry.collection}/${entry.slug}`,
...(maxLines !== undefined && { maxLines }),
...(options?.maxLines !== undefined && { maxLines: options.maxLines }),
...(options?.headingLevel !== undefined && { headingLevel: options.headingLevel }),
};
}

/**
* Transform a blog or project entry into ContentCard props
* Transform a project entry into ContentCard props
*/
export function getProjectCardProps(entry: CollectionEntry<"blog"> | CollectionEntry<"projects">, maxLines?: number | "none") {
export function getProjectCardProps(entry: CollectionEntry<"blog"> | CollectionEntry<"projects">, options?: CardOptions) {
const displayTitle = entry.data.cardTitle || entry.data.title;

return {
title: displayTitle,
subtitle: entry.data.description,
link: `/${entry.collection}/${entry.slug}`,
...(maxLines !== undefined && { maxLines }),
...(options?.maxLines !== undefined && { maxLines: options.maxLines }),
...(options?.headingLevel !== undefined && { headingLevel: options.headingLevel }),
};
}

/**
* Transform a brief entry into ContentCard props
* @param includeCategory - Whether to include the category as a title prefix
* @param maxLines - Maximum number of lines to display for the description, or "none" for unlimited
* @param options - Card display options (maxLines, headingLevel)
*/
export function getBriefCardProps(entry: CollectionEntry<"briefs">, includeCategory = true, maxLines?: number | "none") {
export function getBriefCardProps(entry: CollectionEntry<"briefs">, includeCategory = true, options?: CardOptions) {
const displayTitle = entry.data.cardTitle || entry.data.title;

// Extract category from slug path
const categorySlug = extractCategoryFromSlug(entry.slug);
let categoryPrefix: string | undefined;

if (includeCategory && categorySlug) {
const category = getCategory(categorySlug, `src/content/briefs/${categorySlug}`);
categoryPrefix = category.titlePrefix || category.displayName;
}

return {
titlePrefix: categoryPrefix,
title: displayTitle,
subtitle: entry.data.description,
link: `/${entry.collection}/${entry.slug}`,
...(maxLines !== undefined && { maxLines }),
...(options?.maxLines !== undefined && { maxLines: options.maxLines }),
...(options?.headingLevel !== undefined && { headingLevel: options.headingLevel }),
};
}
10 changes: 5 additions & 5 deletions src/pages/blog/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,21 @@ const ogData = getListOGData(
<PageLayout title={BLOG.TITLE} description={BLOG.DESCRIPTION} ogData={ogData}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
<h1 class="animate font-semibold text-black dark:text-white">
Blog
</div>
</h1>
<div class="space-y-4">
{years.map(year => (
<section class="animate space-y-4">
<div class="font-semibold text-black dark:text-white">
<h2 class="font-semibold text-black dark:text-white">
{year}
</div>
</h2>
<div>
<ul class="flex flex-col gap-4">
{
posts[year].map((post) => (
<li>
<ContentCard {...getBlogCardProps(post, 3)}/>
<ContentCard {...getBlogCardProps(post, { maxLines: 3 })}/>
</li>
))
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/briefs/[category].astro
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const ogData = getListOGData(
<ul class="animate flex flex-col gap-4">
{renderedBriefs.map((brief) => (
<li>
<ContentCard {...getBriefCardProps(brief, false, 3)} />
<ContentCard {...getBriefCardProps(brief, false, { maxLines: 3 })} />
</li>
))}
</ul>
Expand Down
10 changes: 5 additions & 5 deletions src/pages/briefs/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ const ogData = getListOGData(
<PageLayout title={BRIEFS.TITLE} description={BRIEFS.DESCRIPTION} ogData={ogData}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
<h1 class="animate font-semibold text-black dark:text-white">
Briefs
</div>
</h1>
<ul class="flex flex-col space-y-4">
{
brief_categories.map(categoryKey => {
Expand All @@ -91,9 +91,9 @@ const ogData = getListOGData(
return (
<li>
<div class="flex items-center gap-3 mb-2">
<h5 class="font-semibold text-black dark:text-white">
<h2 class="font-semibold text-black dark:text-white">
{ metadata.displayName }
</h5>
</h2>
{hasCategory && (
<span class="text-sm">
<Link href={`/briefs/${metadata.slug}`}>
Expand All @@ -111,7 +111,7 @@ const ogData = getListOGData(
{
categoryBriefs.map((brief: Brief) => (
<li class="animate">
<ContentCard {...getBriefCardProps(brief, false, 3)}/>
<ContentCard {...getBriefCardProps(brief, false, { maxLines: 3 })}/>
</li>
))
}
Expand Down
23 changes: 12 additions & 11 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const ogData = getHomeOGData(

<PageLayout title={HOME.TITLE} description={HOME.DESCRIPTION} ogData={ogData}>
<Container>
<h1 class="sr-only">Dispatches</h1>
<div class="space-y-16">
<section>
<article class="space-y-4">
Expand All @@ -52,62 +53,62 @@ const ogData = getHomeOGData(

<section class="animate space-y-6">
<div class="flex flex-wrap gap-y-2 items-center justify-between">
<h5 class="font-semibold text-black dark:text-white">
<h2 class="font-semibold text-black dark:text-white">
Latest posts
</h5>
</h2>
<Link href="/blog">
See all posts
</Link>
</div>
<ul class="flex flex-col gap-4">
{blog.map(post => (
<li>
<ContentCard {...getBlogCardProps(post, 2)} />
<ContentCard {...getBlogCardProps(post, { maxLines: 2 })} />
</li>
))}
</ul>
</section>

<section class="animate space-y-6">
<div class="flex flex-wrap gap-y-2 items-center justify-between">
<h5 class="font-semibold text-black dark:text-white">
<h2 class="font-semibold text-black dark:text-white">
Recent briefs
</h5>
</h2>
<Link href="/briefs">
See all briefs
</Link>
</div>
<ul class="flex flex-col space-y-4">
{briefs.map(brief => (
<li>
<ContentCard {...getBriefCardProps(brief, true, 2)} />
<ContentCard {...getBriefCardProps(brief, true, { maxLines: 2 })} />
</li>
))}
</ul>
</section>

<section class="animate space-y-6">
<div class="flex flex-wrap gap-y-2 items-center justify-between">
<h5 class="font-semibold text-black dark:text-white">
<h2 class="font-semibold text-black dark:text-white">
Recent projects
</h5>
</h2>
<Link href="/projects">
See all projects
</Link>
</div>
<ul class="flex flex-col gap-4">
{projects.map(project => (
<li>
<ContentCard {...getProjectCardProps(project, 2)} />
<ContentCard {...getProjectCardProps(project, { maxLines: 2 })} />
</li>
))}
</ul>
</section>

<section class="animate space-y-4">
<h5 class="font-semibold text-black dark:text-white">
<h2 class="font-semibold text-black dark:text-white">
Let's Connect
</h5>
</h2>
<article>
<p>
Here's how to get in touch:
Expand Down
6 changes: 3 additions & 3 deletions src/pages/projects/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ const ogData = getListOGData(
<PageLayout title={PROJECTS.TITLE} description={PROJECTS.DESCRIPTION} ogData={ogData}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
<h1 class="animate font-semibold text-black dark:text-white">
Projects
</div>
</h1>
<ul class="animate flex flex-col gap-4">
{
projects.map((project) => (
<li>
<ContentCard {...getProjectCardProps(project, 3)}/>
<ContentCard {...getProjectCardProps(project, { maxLines: 3, headingLevel: 2 })}/>
</li>
))
}
Expand Down
Loading