بنقرة واحدة
add-content-type
Add a new content type to the lqdev.me F# static site generator — complete 8-file checklist
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Add a new content type to the lqdev.me F# static site generator — complete 8-file checklist
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Audit AI Memex health — detect stale entries, spoke-hub drift, broken references, and dependency propagation risks across the knowledge base
Import AI Memex entries from other projects into the central lqdev.me store for publishing
Build and validate the lqdev.me static site generator — dotnet build, dotnet run, and output verification
Search and retrieve knowledge from the AI Memex — find patterns, research, and references across projects
Write knowledge entries to the AI Memex — capture patterns, research, decisions, and project outcomes during any coding session
| name | add-content-type |
| description | Add a new content type to the lqdev.me F# static site generator — complete 8-file checklist |
This skill walks through adding a new content type to the F# static site generator. A content type requires changes in 8 files, in this order.
PersonalSite.fsproj exists)dotnet build passes before startingAdd a new details record type with [<CLIMutable>] and [<YamlMember>] attributes:
[<CLIMutable>]
type MyContentDetails = {
[<YamlMember(Alias="title")>] Title: string
[<YamlMember(Alias="description")>] Description: string
[<YamlMember(Alias="published_date")>] PublishedDate: string
// Add fields as needed
}
Add the wrapper record:
type MyContent = {
FileName: string
Metadata: MyContentDetails
Content: string
MarkdownSource: string option
}
Implement ITaggable if the content type has tags.
Add a parse function (usually a one-liner wrapping the generic parser):
let parseMyContentFromFile (filePath: string) =
parseDocumentFromFile<MyContentDetails> filePath
Create a new file Processors/MyContentProcessor.fs as a top-level module (no =) that
opens GenericBuilder for the core abstraction, then defines a ContentProcessor<MyContent>:
module MyContentProcessor
open GenericBuilder
// ...other opens as needed (Domain, ASTParsing, Giraffe.ViewEngine, ...)
let create () : ContentProcessor<MyContent> = {
Parse = fun filePath -> ...
Render = fun entry -> ...
OutputPath = fun entry -> ...
RenderCard = fun entry -> ...
RenderRss = fun entry -> ...
}
Wire it into PersonalSite.fsproj between GenericBuilder.fs and UnifiedFeeds.fs (after the
other Processors\*.fs entries) — UnifiedFeeds.fs and the page builders consume processors, so
they must compile after. The core ContentProcessor/FeedData types and buildContentWithFeeds
stay in GenericBuilder.fs; reference them as GenericBuilder.FeedData<_> etc.
Also add a convertMyContentToUnified function in UnifiedFeeds.fs (and a feed in buildAllFeeds)
if the content type should appear in the unified feed and/or have its own RSS feed.
Add a buildMyContent delegate in Builders/ContentTypePages.fs (module ContentTypePagesBuilder)
following the existing BuildDriver-based pattern. It orchestrates: load source files → parse →
render → write output, returning the GenericBuilder.FeedData<MyContent> list for the unified feed.
Call the processor as MyContentProcessor.create() (unqualified — it's a top-level module).
Add the build function call in the main pipeline, after data collection
(let myContentFeedData = buildMyContent()), then add one row to the
contentRoster table (the B1 content-type registry):
{ Identity = ContentTypes.ContentType.MyContent; Unified = myContentUnified
InTimeline = true; InAllFeeds = true; InBlogArchive = false }
The timeline / all-content / blog-archive feed lists DERIVE from this roster, so
you set participation once via the flags — there are no separate membership lists
to keep in sync. (Adding the ContentType.MyContent DU case in ContentTypes.fs
serialize/parse/urlPrefix arms is compiler-enforced.) Tag-page
participation (allTaggableContent) and desktop nav (Views/Navigation.fs) remain
explicit and are edited separately when relevant.Create the individual page view function.
Create the collection/listing view for the content type landing page.
Add re-exports if the content type introduces new shared view components.
After all 8 files are updated:
dotnet build — must compile with 0 errorsdotnet run — must generate the site successfully_public/ for correct renderingMediaType.Unknown not Unknown).IgnoreUnmatchedProperties() — new optional string fields
default to null and need no parser changes.fs module to PersonalSite.fsproj respecting compile order:
Processors\*.fs go between GenericBuilder.fs and UnifiedFeeds.fs; Builders\*.fs go
after their dependencies (Views, Services, UnifiedFeeds.fs, BuildDriver.fs) and before Program.fspublished_date, responses use dt_published