Skip to content

Commit c91b403

Browse files
authored
Merge branch 'main' into feat/you-are-not-your-user
2 parents dd8a8fc + 63a92fd commit c91b403

9 files changed

Lines changed: 76 additions & 17 deletions

File tree

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
33
/// <reference types="next/navigation-types/compat/navigation" />
4-
import "./.next/types/routes.d.ts";
4+
import "./.next/dev/types/routes.d.ts";
55

66
// NOTE: This file should not be edited
77
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

public/images/publication-icon.png

633 KB
Loading

scripts/sync-atproto.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,32 @@ async function putRecord(
9595
return res.json() as Promise<{ uri: string }>;
9696
}
9797

98+
type AtprotoBlob = {
99+
$type: 'blob';
100+
ref: { $link: string };
101+
mimeType: string;
102+
size: number;
103+
};
104+
105+
async function uploadBlob(
106+
session: Session,
107+
data: Buffer,
108+
mimeType: string
109+
): Promise<AtprotoBlob> {
110+
const res = await fetch(`${PDS_URL}/xrpc/com.atproto.repo.uploadBlob`, {
111+
method: 'POST',
112+
headers: {
113+
'Content-Type': mimeType,
114+
Authorization: `Bearer ${session.accessJwt}`,
115+
},
116+
// Node.js fetch accepts Buffer at runtime; cast needed due to strict lib types
117+
body: data as unknown as BodyInit,
118+
});
119+
if (!res.ok) throw new Error(`uploadBlob failed: ${await res.text()}`);
120+
const json = (await res.json()) as { blob: AtprotoBlob };
121+
return json.blob;
122+
}
123+
98124
// ─── Local data helpers ──────────────────────────────────────────────────────
99125

100126
type AtprotoData = {
@@ -141,7 +167,11 @@ function readContentDirectory(dir: string): ContentFrontmatter[] {
141167
.map((file) => {
142168
const raw = fs.readFileSync(path.join(dirPath, file), 'utf-8');
143169
const { data } = matter(raw);
144-
return data as ContentFrontmatter;
170+
const fm = data as ContentFrontmatter;
171+
// Always use filename-derived slug to match Next.js routing, which uses
172+
// filenames not frontmatter slugs when building static paths.
173+
fm.slug = path.basename(file, path.extname(file));
174+
return fm;
145175
})
146176
.filter(
147177
(fm): fm is ContentFrontmatter =>
@@ -161,15 +191,27 @@ const CREATE_ONLY = process.argv.includes('--create-only');
161191
// When present, only those files are synced instead of the full directory scan.
162192
const TARGET_PATHS = process.argv.slice(2).filter((a) => !a.startsWith('--'));
163193

194+
const ICON_PATH = path.join(process.cwd(), 'public/images/publication-icon.png');
195+
164196
async function syncPublication(
165197
session: Session,
166198
data: AtprotoData
167199
): Promise<string> {
200+
let icon: AtprotoBlob | undefined;
201+
if (fs.existsSync(ICON_PATH)) {
202+
const imgData = fs.readFileSync(ICON_PATH);
203+
icon = await uploadBlob(session, imgData, 'image/png');
204+
console.log(' Uploaded publication icon blob');
205+
} else {
206+
console.log(` No icon found at ${ICON_PATH}, skipping`);
207+
}
208+
168209
const record = buildPublicationRecord({
169210
url: BASE_SITE_URL,
170-
name: 'Mike Bifulco',
211+
name: '💌 Tiny Improvements - for builders, by @MikeBifulco.com',
171212
description:
172213
'Resources for modern software designers and developers. Tips and walkthroughs on React, node, and javascript.',
214+
icon,
173215
});
174216

175217
if (data.publicationUri) {
@@ -244,13 +286,11 @@ async function main() {
244286
const raw = fs.readFileSync(path.join(process.cwd(), filePath), 'utf-8');
245287
const { data: fm } = matter(raw);
246288
const frontmatter = fm as ContentFrontmatter;
289+
// Use filename as slug, consistent with Next.js static path generation.
290+
frontmatter.slug = path.basename(filePath, path.extname(filePath));
247291

248-
if (
249-
!frontmatter.slug ||
250-
!frontmatter.title ||
251-
frontmatter.published === false
252-
) {
253-
console.log(` Skipped (unpublished or missing slug): ${filePath}`);
292+
if (!frontmatter.title || frontmatter.published === false) {
293+
console.log(` Skipped (unpublished or missing title): ${filePath}`);
254294
continue;
255295
}
256296

src/components/seo.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ const SEO: React.FC<SEOProps> = ({
8989
/>
9090
{/* standard.site AT Protocol links */}
9191
{standardSitePublicationUri && (
92-
<link rel="site.standard.publication" href={standardSitePublicationUri} />
92+
<link
93+
rel="site.standard.publication"
94+
href={standardSitePublicationUri}
95+
/>
9396
)}
9497
{standardSiteDocumentUri && (
9598
<link rel="site.standard.document" href={standardSiteDocumentUri} />

src/data/atproto-documents.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"next-js-github-bio-about-page": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7c2mv2l",
4242
"nullish-coalescing-javascript": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7dhkb25",
4343
"on-normalcy": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7eomf2l",
44-
"operating-principles": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7fymb2p",
4544
"orton-effect-css-react": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7hlf22s",
4645
"own-your-work-with-canonical-tags": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7jc4m2c",
4746
"patching-npm-dependencies-with-pnpm-patch": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3e7kk4l24",
@@ -68,7 +67,6 @@
6867
"sticker-update-we-raised-176-nzd": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3ealjrt24",
6968
"structured-data-json-ld-for-next-js-sites": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3ean5jk2x",
7069
"text-wrap-balance-will-make-your-designs-better": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3eaqqrc2l",
71-
"text-wrap-pretty-will-make-your-designs-better": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3earxtd2g",
7270
"twitch-streaming-software-development-lessons": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3eatgpc2l",
7371
"twitter-and-the-perils-of-obedience": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3eauum32g",
7472
"typescript-vscode-error-fix-last-resort": "at://did:plc:icpcpp5txyow3prnfgi533lj/site.standard.document/3mmx3eawcir25",

src/data/posts/text-wrap-pretty-for-subtle-visual-balance.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
title: 'CSS Tips Part 2 - text-wrap: pretty will make your text feel intentional'
33
excerpt: 'Go beyond balance. Learn how text-wrap: pretty improves readability, reduces awkward breaks, and makes your UI feel more polished.'
44
tags: [css, ux, typography]
5-
slug: text-wrap-pretty-will-make-your-designs-better
5+
slug: text-wrap-pretty-for-subtle-visual-balance
66
series: CSS For Visual Balance
7-
coverImagePublicId: posts/text-wrap-pretty-will-make-your-designs-better/cover
7+
coverImagePublicId: posts/text-wrap-pretty-for-subtle-visual-balance/cover
88
date: 04-30-2026
99
---
1010

src/pages/newsletter/[slug].tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export const getStaticProps: GetStaticProps<
4949
params.slug
5050
);
5151

52-
const standardSitePublicationUri = (atprotoData as { publicationUri: string }).publicationUri;
52+
const standardSitePublicationUri = (atprotoData as { publicationUri: string })
53+
.publicationUri;
5354

5455
return {
5556
props: {

src/pages/posts/[slug].tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ export const getStaticProps: GetStaticProps<
5454
params.slug
5555
);
5656

57-
const standardSitePublicationUri = (atprotoData as { publicationUri: string }).publicationUri;
57+
const standardSitePublicationUri = (atprotoData as { publicationUri: string })
58+
.publicationUri;
5859

5960
return {
6061
props: {
@@ -80,7 +81,13 @@ export async function getStaticPaths() {
8081
};
8182
}
8283

83-
const PostPage: NextPage<PostPageProps> = ({ post, series, relatedContent, standardSiteDocumentUri, standardSitePublicationUri }) => {
84+
const PostPage: NextPage<PostPageProps> = ({
85+
post,
86+
series,
87+
relatedContent,
88+
standardSiteDocumentUri,
89+
standardSitePublicationUri,
90+
}) => {
8491
const { frontmatter } = post;
8592

8693
const { coverImagePublicId, published, date, tags, title, excerpt, slug } =

src/utils/atproto.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,33 @@ export type AtprotoDocumentRecord = {
1010
publishedAt: string;
1111
};
1212

13+
type AtprotoBlob = {
14+
$type: 'blob';
15+
ref: { $link: string };
16+
mimeType: string;
17+
size: number;
18+
};
19+
1320
export type AtprotoPublicationRecord = {
1421
$type: 'site.standard.publication';
1522
url: string;
1623
name: string;
1724
description?: string;
25+
icon?: AtprotoBlob;
1826
};
1927

2028
export function buildPublicationRecord(opts: {
2129
url: string;
2230
name: string;
2331
description?: string;
32+
icon?: AtprotoBlob;
2433
}): AtprotoPublicationRecord {
2534
return {
2635
$type: 'site.standard.publication',
2736
url: opts.url,
2837
name: opts.name,
2938
...(opts.description ? { description: opts.description } : {}),
39+
...(opts.icon ? { icon: opts.icon } : {}),
3040
};
3141
}
3242

0 commit comments

Comments
 (0)