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
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ production: the Vercel project env). Without the key the fetch is skipped.
## CI and Publishing

- Depot CI lives in `.depot/workflows/ci.yml`.
- 2026-07-06: Turbo >=2.9.17 ignores `peerDependencies` when building the
package graph (vercel/turborepo#13025), so convex-email's
`@opencoredev/email-sdk` peer range no longer creates the
`convex-email#test -> email-sdk#build` edge on its own. convex-email
therefore also lists `@opencoredev/email-sdk` as a `workspace:*` dev
dependency; keep that entry, or `turbo test` runs the convex-email tests
against an unbuilt email-sdk and CI fails with "Cannot find module".
Verify edges with `bunx turbo run test --dry=json`.
- Release publishing lives in `.github/workflows/release.yml`.
- npm publishing uses GitHub-hosted Actions for Trusted Publishing/OIDC.
- Do not move npm publishing to Depot unless npm supports Depot as a trusted publisher or the project intentionally switches to token-based publishing.
24 changes: 12 additions & 12 deletions apps/fumadocs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,33 @@
"@tanstack/react-router-devtools": "1.167.0",
"@tanstack/react-start": "1.168.13",
"@tanstack/start-static-server-functions": "1.167.9",
"@usenotra/sdk": "^1.2.2",
"@usenotra/sdk": "^1.3.1",
"@vercel/analytics": "^2.0.1",
"fumadocs-core": "16.9.1",
"fumadocs-mdx": "15.0.9",
"fumadocs-ui": "16.9.1",
"lucide-react": "^1.16.0",
"lucide-react": "^1.23.0",
"marked": "^18.0.5",
"posthog-js": "^1.386.6",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"sanitize-html": "^2.17.5",
"shiki": "4.1.0",
"tailwind-merge": "^3.6.0",
"vite": "^8.0.14"
"vite": "^8.1.3"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.0",
"@types/mdx": "^2.0.13",
"@types/node": "^25.9.1",
"@types/react": "^19.2.15",
"@tailwindcss/vite": "^4.3.2",
"@types/mdx": "^2.0.14",
"@types/node": "^25.9.4",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/sanitize-html": "^2.16.1",
"@vitejs/plugin-react": "^6.0.2",
"@vitejs/plugin-react": "^6.0.3",
"nitro": "3.0.260522-beta",
"serve": "^14.2.6",
"srvx": "^0.11.16",
"tailwindcss": "^4.3.0",
"srvx": "^0.11.20",
"tailwindcss": "^4.3.2",
"typescript": "^6.0.3"
}
}
14 changes: 10 additions & 4 deletions apps/fumadocs/scripts/notra-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import type { BlogPost } from "../src/lib/blog-types";

// Minimum surface we rely on from a Notra `listPosts` post. Typed structurally
// so we don't depend on deep SDK subpath imports.
// 2026-07-06: @usenotra/sdk 1.3.x made `markdown` nullable — it is null for
// image-type posts, whose `content` is a CDN image URL rather than a body.
export type NotraPostInput = {
id: string;
title: string;
slug: string | null;
content: string;
markdown: string;
markdown: string | null;
status: string;
createdAt: string;
updatedAt: string;
Expand Down Expand Up @@ -181,15 +183,19 @@ export function renderMarkdownToSafeHtml(markdown: string): string {
}).trim();
}

// Maps one published Notra post into the blog shape. Returns null for drafts or
// posts with no usable title so the caller can skip them.
// Maps one published Notra post into the blog shape. Returns null for drafts,
// posts with no usable title, or posts without a Markdown body so the caller
// can skip them.
export function mapNotraPost(input: NotraPostInput, seenSlugs: Set<string>): MappedPost | null {
if (input.status !== "published") return null;

const title = input.title?.trim();
if (!title) return null;

const markdown = input.markdown ?? "";
// Image-type posts have `markdown: null` (their `content` is an image URL,
// not prose). The blog can only render Markdown bodies, so skip them.
const markdown = input.markdown;
if (markdown === null) return null;
// Always slugify, even an explicit Notra slug: a value like "release/notes"
// would otherwise break the single-segment /blog/$slug route, the OG image
// path, and prerender enumeration.
Expand Down
10 changes: 10 additions & 0 deletions apps/fumadocs/src/lib/blog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ describe("mapNotraPost", () => {
test("skips drafts", () => {
expect(mapNotraPost(notraPost({ status: "draft" }), new Set())).toBeNull();
});

test("skips posts without a Markdown body (image-type posts)", () => {
// @usenotra/sdk 1.3.x: image posts carry `markdown: null` and put an image
// URL in `content` — there is no prose body for the blog to render.
const mapped = mapNotraPost(
notraPost({ markdown: null, content: "https://cdn.usenotra.com/posts/post_1.png" }),
new Set(),
);
expect(mapped).toBeNull();
});
});

describe("getBlogPostMetaTitle", () => {
Expand Down
Loading