Repository navigation
feat(content): ✨ Content Containers (Stage 2) - #875
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Containers organize content into hierarchical collections - forum boards,
blog sections, knowledge-base categories - without a content type per
category.
- `defineContentContainer({ id, fields, hierarchy, ordering, publicApi, admin })`
with registration-time validation and typed metadata inferred from fields.
- One core table, `core_content_containers` (adjacency list + sibling
position + schema-validated `metadata`), so a definition adds no migration.
- Tree service: create, update, move, reorder, delete, tree, ancestors,
children and descendants. Every write takes a per-definition advisory lock
and validates the whole plan first, so refused or concurrent writes cannot
corrupt the tree. Deletion refuses containers with children or content.
- `field.container({ definition, required })`: one container per record, an
indexed RESTRICT foreign key, validated against the collection on every
admin, editorial and public write.
- publicWrite: the container may be chosen on create, never on update; it is
resolved server-side and handed to the publication policy hook.
- Public reads: container list/detail routes, and `?<field>=<id>` with
`includeDescendants=true` on content lists. AdminCP CRUD + move routes per
definition, gated by its own staff permission module.
- Container admin bodies the schema refuses answer
`{ code: "invalidMetadata" | "invalidInput", message }` through an optional
`buildRoute` validation `hook`; namespace checks key owners by kind and id.
- Boot validation across plugins, page-cache tags and
`content.container.*` events.
- AdminCP: a tree editor per collection at `/admin/containers/{admin.path}`
with drag-and-drop reorder/move (same planner as the server, optimistic
with rollback), keyboard and screen-reader support, a create/edit sheet
generated from the definition's fields, and a container picker in
generated content forms.
- Example plugin: `example.boards` with `/boards` and `/boards/:slug`, a
required `board` field on discussions, and a migration that files existing
discussions under a seeded "General" board.
- Docs for containers, hierarchy and frontend pages, plus field, public API,
public write and event reference updates.
- Tests for definitions, tree planners, the service, admin and public routes,
assignment on writes and the container filter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aXenDeveloper
force-pushed
the
claude/content-containers
branch
from
October 13, 2026 14:44
26469e5 to
665682a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Improving Documentation
oxfmt,oxlint --type-aware).content-engine/containers.mdx,container-hierarchy.mdxandcontainer-frontend.mdx, in a new "Organize content" group. These pages were also updated:fields.mdx,reference.mdx,public-api.mdx,public-write.mdx,public-write-api.mdx,events.mdx,index.mdx,plugin-files.mdxanddev/events/built-in-events.mdx.Description
What?
Content Containers organize content into hierarchical collections (forum boards, blog sections, knowledge-base categories), so you don't need one content type per category. They are built into the Content Engine and are not tied to forums.
Developer API
Architecture
core_content_containers. It is an adjacency list (parentId, RESTRICT) with a siblingposition, a slug unique per definition, andmetadatajsonb that is checked against the definition's own fields on every write. A definition is code, not schema, so adding one needs no migration.createContentContainerService): create, update, move, reorder, delete, list, children, ancestors and descendants.maxDepth(it counts the height of the subtree being moved), parents from another definition, out-of-range positions, duplicate slugs, and deleting a container that has children or content.codethe AdminCP translates.field.container()is a new field kind, so it reuses the column, schema, form, filter and public-projection pipeline.(container, publishedAt)when the type uses publication.create.fieldsand is refused inupdate.fieldsat definition time, so authors can't move content. It is resolved on the server inside the write transaction and passed toresolveContentPublicationPolicyascontainer. That is the hook for future per-container publishing modes.content/<path>GET /returns the collection in tree order (?parent=root|<id>). Nest it withcontentContainerTree.GET /{idOrSlug}returns the container, its ancestors and its children.publicApi.fieldsmetadata is exposed.?<field>=<id>&includeDescendants=true. An unknown container returns 404./admin/containers/*is a dnd-kit tree editor (reorder, re-parent, invalid drops blocked, optimistic update with rollback).can_view/create/edit/delete).content.container.created|updated|moved|deletedand expire the page cache./boards(tree),/boards/:slug(discussions including sub-boards), and a board picker on "New discussion".20261013103054_add_content_containersis hand-adjusted. It addsboardas nullable, files any existing discussions under a seeded "General" board, then setsNOT NULL.Why?
Stage 2 of the Content Engine roadmap. Plugins (forum next) need one content type (
forum.topic) spread across many admin-managed, nested containers, with server-side checks on where content may be filed. The schema-drivenmetadataand thecontainercontext in the write pipeline leave room for Stage 3 (per-container publishing modes, approval, permissions, visibility) without changing the data model.Verification
vitestunit + isolated + dom: 588 files, 9,261 tests passed.--project types: 429 passed, no type errors.tsc --noEmitis clean for core,apps/webandplugins/example. Theintegrationproject (real builds/child processes) was not run.includeDescendants; PATCH can't move content; deleting a board with content refused.Known limitations / follow-ups
includeDescendants) aren't part of the fetcher's inferred query type, becausepublicFiltersis an untypedZodObject. This was already true for every filterable field.can_viewto use the container picker in content forms.🤖 Generated with Claude Code
https://claude.ai/code/session_01JvepU3TcQSG6hHpguYPsHd
Generated by Claude Code