a tool for shared writing and social publishing
Simplification 2: Split DocumentContext and LeafletContentContext#
Summary#
Separate the current PostPageContext into two contexts:
- DocumentContext - Always available, contains document metadata
- LeafletContentContext - Only provided when content type is
pub.leaflet.content
This moves the content-type check from ~15 scattered components to a single branching point.
Current State#
Components throughout the tree do their own format checking:
// In LinearDocumentPage.tsx, PostPages.tsx, Quotes.tsx, etc.
const record = document?.normalizedDocument;
const pages = record ? getDocumentPages(record) : undefined;
if (!document || !record || !pages) return null;
This pattern is:
- Repetitive (~15 call sites)
- Error-prone (easy to forget the check)
- Not future-proof (each site needs updating for new content types)
Desired State#
New Context Definitions#
// contexts/DocumentContext.tsx
type DocumentContextValue = {
uri: string;
normalizedDocument: NormalizedDocument;
normalizedPublication: NormalizedPublication | null;
theme: Theme | null;
prevNext?: { prev?: { uri: string; title: string }; next?: { uri: string; title: string } };
quotesAndMentions: QuotesAndMentions;
};
const DocumentContext = createContext<DocumentContextValue | null>(null);
export function useDocument() {
const ctx = useContext(DocumentContext);
if (!ctx) throw new Error("useDocument must be within DocumentProvider");
return ctx;
}
// contexts/LeafletContentContext.tsx
type LeafletContentContextValue = {
pages: Page[];
};
const LeafletContentContext = createContext<LeafletContentContextValue | null>(null);
export function useLeafletContent() {
const ctx = useContext(LeafletContentContext);
if (!ctx) throw new Error("useLeafletContent must be within LeafletContentProvider");
return ctx;
}
// Optional: non-throwing version for components that handle both cases
export function useLeafletContentOptional() {
return useContext(LeafletContentContext);
}
Branching Point#
// In DocumentPageRenderer.tsx or similar top-level component
function DocumentRenderer({ document }: { document: PostPageData }) {
const { normalizedDocument, normalizedPublication } = document;
if (!normalizedDocument) {
return <NotFound />;
}
const documentContextValue = {
uri: document.uri,
normalizedDocument,
normalizedPublication,
theme: document.theme,
prevNext: document.prevNext,
quotesAndMentions: document.quotesAndMentions,
};
// Branch based on content type
if (hasLeafletContent(normalizedDocument)) {
return (
<DocumentContext.Provider value={documentContextValue}>
<LeafletContentContext.Provider value={{ pages: normalizedDocument.content.pages }}>
<LeafletDocumentPage />
</LeafletContentContext.Provider>
</DocumentContext.Provider>
);
}
// Future: handle other content types
// if (hasSomeOtherContent(normalizedDocument)) {
// return <SomeOtherDocumentPage />;
// }
return <UnsupportedContentType type={normalizedDocument.content?.$type} />;
}
Simplified Child Components#
// Before
function LinearDocumentPage({ document }: Props) {
const record = document?.normalizedDocument;
const pages = record ? getDocumentPages(record) : undefined;
if (!document || !record || !pages) return null;
// use pages...
}
// After
function LinearDocumentPage() {
const { pages } = useLeafletContent(); // guaranteed non-null
const { theme, normalizedPublication } = useDocument();
// use pages directly...
}
Files to Update#
New Files#
contexts/DocumentContext.tsxcontexts/LeafletContentContext.tsx
Update Branching Points#
app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsxapp/lish/[did]/[publication]/[rkey]/page.tsxapp/p/[didOrHandle]/[rkey]/page.tsx
Simplify Consumers (remove getDocumentPages checks)#
app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsxapp/lish/[did]/[publication]/[rkey]/PostPages.tsxapp/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsxapp/lish/[did]/[publication]/[rkey]/Interactions/Quotes.tsxcomponents/Blocks/PublicationPollBlock.tsxcomponents/Canvas.tsxcomponents/Pages/PublicationMetadata.tsx
Benefits#
- Single point of format checking - Content type is checked once, not 15 times
- Type safety - Components using
useLeafletContent()know pages is non-null - Future-proof - New content types get their own branch and components
- Explicit contracts - Components declare their content-type dependency via which context they consume
- Better errors - Throwing in
useLeafletContent()catches developer mistakes (wrong component in wrong context)
Migration Strategy#
- Create the new context files
- Update
DocumentPageRendererto be the branching point - Migrate child components one at a time, replacing
getDocumentPageschecks withuseLeafletContent() - Remove
getDocumentPagescalls from the codebase - Eventually deprecate the old
PostPageContextpattern