Instatic Content Workspace Collections UI Architecture: A Deep Dive into the Hook-Driven Design
The Instatic content workspace collections UI architecture centers on a single useContentWorkspace hook that acts as the single source of truth for collection data, selection state, and CRUD operations, while specialized presentational components in the sidebar, toolbar, and dialogs consume this unified data layer.
The Content workspace in Instatic is where editors manage collections—the post types that store CMS entries. Understanding how the UI orchestrates collection loading, selection, and mutation helps developers extend the admin interface or build custom collection management features. This architecture follows a clean separation between data logic and presentation, implemented through React hooks and modular components.
Core Data Layer: The useContentWorkspace Hook
At the heart of the Instatic content workspace collections UI architecture lies the useContentWorkspace hook located in src/admin/pages/content/hooks/useContentWorkspace.ts. This hook serves as the single source of truth for all collection-related state.
Loading and Selection
The hook initializes by fetching all tables where kind === 'postType' via the loadCollections() function. It maintains two critical pieces of state:
collections: An array of collection objects representing the available post typesselectedCollectionId: The ID of the currently active collection
The hook computes a derived selectedCollection value based on the current selection, ensuring components always access consistent data without additional memoization (React Compiler handles optimization).
CRUD Operations and Entry Helpers
The hook exposes atomic operations that mutate local state and persist changes to the CMS API:
createCollection: WrapscreateCmsDataTableand updates the localcollectionsarrayupdateCollection: CallsupdateCmsDataTableand merges changes into state- **
deleteCollection: InvokesdeleteCmsDataTableand filters the local array - Entry-level helpers: Functions like
moveEntryToCollectionandcreateEntrydelegate to a genericwithEntryOphelper while keeping the workspace synchronized
All setters use standard React useState calls, returning a stable object that UI components can destructure safely.
Page Layout and Navigation Structure
The top-level ContentPage.tsx (src/admin/pages/content/ContentPage.tsx) orchestrates the workspace layout and wires the data hook to the presentation layer.
export function ContentPage() {
const workspace = useContentWorkspace({ permissionUser });
// Security gating with step-up check
runStepUp(() => {
// Render workspace
});
return (
<>
<ContentSidebar workspace={workspace} />
<ContentToolbar selectedCollection={workspace.selectedCollection} />
<ContentDocumentCanvas workspace={workspace} />
</>
);
}
The page component also gates collection-level mutations behind a step-up security check using runStepUp, ensuring only privileged users can create, update, or delete collections.
Collection Navigation Components
Sidebar and Explorer Panel
The ContentSidebar.tsx component (src/admin/pages/content/components/ContentSidebar/ContentSidebar.tsx) renders the Collections section with proper accessibility markup (<section aria-label="Collections">). It displays each collection row with entry counts and highlights the currently selected collection.
The sidebar wires hook callbacks directly to UI events:
<ContentExplorerPanel
selectedCollection={workspace.selectedCollection}
selectedCollectionId={workspace.selectedCollectionId}
collections={workspace.collections}
canCreateCollection={canManageCollections}
onSelectCollection={workspace.selectCollection}
createCollection={() => setCollectionDialogOpen(true)}
updateCollection={workspace.updateCollection}
deleteCollection={workspace.deleteCollection}
moveEntryToCollection={workspace.moveEntryToCollection}
/>
Toolbar Controls
ContentToolbar.tsx (src/admin/pages/content/components/ContentToolbar/ContentToolbar.tsx) provides quick actions for the active collection. It renders a collection selector, mode toggle (list vs. canvas), and a create-entry button that dynamically disables when no collection is selected:
<Button
variant="primary"
size="md"
onClick={onCreateEntry}
disabled={!selectedCollection}
>
New {selectedCollection?.singularLabel ?? 'Entry'}
</Button>
Settings Panel
The ContentSettingsPanel.tsx component allows per-collection configuration such as toggling SEO fields, featured media support, and custom fields. It receives selectedCollection and onCollectionChange props, calling the workspace update handlers when users modify collection settings.
Collection Management Dialogs
The architecture separates modal concerns into dedicated dialog components that receive workspace callbacks and close themselves on success.
Create Dialog: ContentCollectionCreateDialog.tsx (src/admin/pages/content/components/ContentCollectionCreateDialog/ContentCollectionCreateDialog.tsx) handles new collection creation.
Settings Dialog: ContentCollectionSettingsDialog.tsx (src/admin/pages/content/components/ContentCollectionSettingsDialog/ContentCollectionSettingsDialog.tsx) manages editing existing collections.
Both dialogs receive createCollection, updateCollection, and deleteCollection callbacks from the workspace hook. They are rendered conditionally from ContentPage via local state flags (collectionDialogOpen, collectionSettingsOpen), keeping the dialog lifecycle separate from the workspace state machine.
State Persistence and Layout Storage
Workspace layout state—such as panel sizes and active panel configuration—persists across sessions through a generic workspace layout store. The content workspace uses useWorkspaceLayout from src/admin/state/workspaceLayout.ts and src/admin/state/workspaceLayoutStorage.ts to read and write panel configurations.
This separation allows the collection UI to remain responsive to resize events while maintaining user-customized layouts without polluting the collection data layer.
Data Flow and Interaction Patterns
The interaction flow follows a predictable unidirectional pattern:
- Mount:
ContentPagecallsuseContentWorkspace, which triggersloadCollections()to fetch post types from the CMS API. - Render:
ContentSidebardisplays the list; user selection callsworkspace.selectCollection, updatingselectedCollectionId. - Propagation: Toolbar and canvas components react to the new
selectedCollectionvalue automatically. - Mutation: User actions trigger dialog opens; submission calls workspace CRUD methods (e.g.,
workspace.createCollection), which update local state and persist tocreateCmsDataTableendpoints. - Sync: All subscribed components re-render with updated collection data thanks to the shared hook state.
Implementation Examples
Consuming the Hook in Custom Components
import { useContentWorkspace } from '@content/hooks/useContentWorkspace';
export function MyCollectionList() {
const { collections, selectedCollectionId, selectCollection } = useContentWorkspace({
permissionUser: currentUser,
});
return (
<ul>
{collections.map(col => (
<li
key={col.id}
style={{ fontWeight: col.id === selectedCollectionId ? 'bold' : 'normal' }}
onClick={() => selectCollection(col.id)}
>
{col.pluralLabel} ({col.entryCount ?? 0})
</li>
))}
</ul>
);
}
Programmatic Collection Creation
import { useContentWorkspace } from '@content/hooks/useContentWorkspace';
import { CreateDataTableInput } from '@core/types';
export function AddBlogCollection() {
const { createCollection } = useContentWorkspace({ permissionUser: currentUser });
const newCollection: CreateDataTableInput = {
name: 'Blog posts',
slug: 'blog',
kind: 'postType',
fields: [], // start empty; UI will add defaults later
};
async function handleClick() {
await createCollection(newCollection);
// UI updates automatically because `collections` state is refreshed
}
return <button onClick={handleClick}>Add “Blog” collection</button>;
}
Summary
- Centralized State: The
useContentWorkspacehook insrc/admin/pages/content/hooks/useContentWorkspace.tsmanages all collection data, selection, and CRUD operations as the single source of truth. - Component Structure:
ContentPage.tsxorchestrates the layout, delegating toContentSidebar,ContentToolbar, and dialog components that receive workspace callbacks via props. - Security Integration: Collection mutations are gated behind step-up authentication checks in the page component.
- Persistence Separation: Layout state persists through
workspaceLayoutStorage.tsandworkspaceLayout.ts, keeping collection data and UI layout concerns decoupled. - Reactivity Model: Standard React
useStatewith React Compiler optimization ensures components re-render efficiently when collection data changes.
Frequently Asked Questions
Where is the collection data actually fetched in the Instatic content workspace?
Collection data is fetched inside src/admin/pages/content/hooks/useContentWorkspace.ts via the loadCollections() function, which queries the CMS API for all tables where kind === 'postType'. This occurs when the hook initializes, typically when ContentPage mounts.
How does the sidebar know which collection is currently selected?
The ContentSidebar component receives selectedCollectionId and selectedCollection from the useContentWorkspace hook passed down via props. The hook maintains this state internally and updates it when selectCollection() is called, causing all subscribed components to re-render with the new selection.
Can I customize the collection creation dialog without modifying the core hook?
Yes. The ContentCollectionCreateDialog.tsx component is a presentational layer that receives the createCollection callback from the workspace hook. You can extend or replace this dialog component while maintaining the same interface—calling the workspace method on submission—to preserve the architecture's data integrity.
What happens to the workspace layout when I refresh the browser?
Panel sizes and active workspace configurations persist across sessions through the workspace layout store in src/admin/state/workspaceLayoutStorage.ts and src/admin/state/workspaceLayout.ts. The content workspace uses the useWorkspaceLayout hook to read and write these settings independently of collection data.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →