feat(admin): allow hiding a collection from the admin sidebar - #2264
feat(admin): allow hiding a collection from the admin sidebar#2264DavidPivert wants to merge 2 commits into
Conversation
Plugins that own a collection end to end — autopopulated entries plus their own admin page — had no way to suppress the auto-generated CRUD entry the sidebar builds from the manifest. Editors saw raw collections they never use next to the ones they actually edit, and the only workarounds were CSS injection or renaming the label to discourage clicks. Adds a `hidden` flag on the collection definition (seed file, schema API, and the collection row). The flag is deliberately scoped to navigation only: the collection still ships in the manifest and stays reachable through the REST API, MCP tools, plugin hooks, and its editor at /content/:collection, so plugins keep managing the data and admins can still navigate there directly. Closes emdash-cms#1131 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 0a15358 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
This is a well-scoped, additive feature that fits EmDash's architecture: a single hidden flag on _emdash_collections, persisted through schema/seed APIs, and consumed only in the admin sidebar while the collection stays in the manifest and reachable by URL/API. I read the diff, the changed files, the schema/seed/runtime handlers, the admin sidebar and manifest types, the REST/OpenAPI schemas, and the migrations.
The implementation is largely correct: the migration is guarded by columnExists like its peers, updateCollection preserves the stored value when omitted, the runtime manifest only emits hidden when set, and the new visibleCollectionEntries helper is pure and unit-tested. There are, however, a few contract and convention gaps that need fixing before merge:
- The OpenAPI/response schema for collections (
collectionSchema) was not updated to includehidden, so generated API docs and clients won't see the field even though the server returns it. - The full schema export endpoint used by
emdash typesalso omitshiddenfrom its emitted collection shape. - The admin client's
AdminManifesttype doesn't declarehiddenon collections, so the sidebar consumes a field that TypeScript doesn't know about. - Several new comments explain/justify decisions or reference issue
#1131, which violates AGENTS.md's comment discipline.
None of these are runtime breakages, but the first three are real API/type-contract regressions for consumers of the schema API and manifest. The comment items are straightforward cleanups.
Findings
-
[needs fixing]
packages/core/src/api/schemas/schema.ts:183The runtime
Collectiontype now has a requiredhiddenboolean, and the schema handlers return it, but the OpenAPI response schemacollectionSchemadoes not declare it. Generated API documentation and clients will miss the field.hasSeo: z.boolean(), hidden: z.boolean(), createdAt: z.string(),(Other collection fields such as
commentsEnabledare also missing here, but that gap predates this PR;hiddenis the new field that must be added now.) -
[needs fixing]
packages/core/src/astro/routes/api/schema/index.ts:81The full schema export endpoint used by the CLI (
emdash types) manually builds each collection object and omits the newhiddenflag. Since this is part of the schema REST surface, exported types and drift checks won't reflect hidden collections.supports: c.supports, hidden: c.hidden, fields: c.fields.map((f) => ({ -
[needs fixing]
packages/admin/src/lib/api/client.ts:95The admin manifest type used by
fetchManifest()does not declarehiddenon collections, so the sidebar reads a property that TypeScript doesn't know exists. Add the optional flag to keep the client contract in sync with the runtimeEmDashManifest.supports: string[]; hasSeo: boolean; urlPattern?: string; hidden?: boolean; fields: Record< -
[needs fixing]
packages/core/src/emdash-runtime.ts:2365-2366This two-line comment justifies a design decision rather than explaining non-obvious code. AGENTS.md: comments should not justify decisions or narrate rejected alternatives. The code (
...(collection.hidden ? { hidden: true } : {})) is self-explanatory once the manifest contract is documented onManifestCollection.Delete lines 2365–2366:
hasSeo: collection.hasSeo, urlPattern: collection.urlPattern, ...(collection.hidden ? { hidden: true } : {}), -
[needs fixing]
packages/admin/tests/components/Sidebar.test.tsx:109Test descriptions are comments read by future maintainers; AGENTS.md forbids referencing issues/PRs in comments. Remove the
#1131reference from the describe title.describe("visibleCollectionEntries", () => { -
[needs fixing]
packages/core/tests/unit/schema/registry.test.ts:131-160These four new test titles reference issue
#1131. AGENTS.md says comments must not reference issues, PRs, or review threads. Remove the#1131:prefix from eachit(...)title (lines 131, 137, 153, 160).it("collections are visible in the sidebar by default", async () => { it("creates a collection hidden from the sidebar", async () => { it("toggles hidden on an existing collection", async () => { it("preserves hidden when an update omits it", async () => { -
[needs fixing]
packages/core/tests/unit/seed/apply.test.ts:166-191These two new test titles reference issue
#1131. AGENTS.md says comments must not reference issues, PRs, or review threads. Remove the#1131:prefix from eachit(...)title (lines 166 and 191).it("applies the hidden flag from the seed", async () => { it("updates the hidden flag when re-applying with onConflict update", async () => {
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
Review follow-up. The flag was returned by the handlers but missing from three published contracts: the OpenAPI collection response schema, the full schema export the CLI reads for `emdash types`, and the admin client's manifest type — which typechecked only because the sidebar declared its own inline shape. Also drops a comment that justified a decision rather than explaining the code, and the issue references from test titles, per AGENTS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What does this PR do?
Adds a
hiddenflag on a collection definition that omits its auto-generated entry from the admin sidebar.Plugins that own a collection end to end — autopopulated entries plus their own admin page — had no way to suppress the CRUD entry the sidebar builds from the manifest, so editors saw raw collections they never use next to the ones they actually edit. The only workarounds were CSS injection or renaming the label to discourage clicks.
The flag is deliberately scoped to navigation only. A hidden collection is still shipped in the manifest and stays reachable through the REST API, MCP tools, plugin hooks, and its editor at
/content/:collection— so plugins keep managing the data and admins can still navigate there directly. Schema, migrations, and query behavior are untouched.Set it in a seed file:
{ "slug": "contact_submissions", "label": "Contact Submissions", "hidden": true, "fields": [] }…or through the schema API (
POST/PATCH /api/schema/collections).Implementation notes:
054_collection_hiddenadds ahiddencolumn to_emdash_collections,NOT NULL DEFAULT 0, guarded bycolumnExistslike053. Existing collections stay visible.updateCollectionpreserves the stored value when the input omitshidden, matching thecommentsEnabledprecedent.hiddenwhen it is set, so the payload is unchanged for the common visible collection.Sidebar.tsxfilters through a new exported pure helpervisibleCollectionEntries, following the pattern the existing sidebar tests rely on (pure artefacts rather than DOM coupling against Kumo's portaled Sidebar primitive).Closes #1131
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Screenshots / test output
No admin UI surface was added, so there is nothing new to screenshot — the change removes nav entries for collections flagged
hidden.Targeted tests (9 new):
packages/core/tests/unit/schema/registry.test.ts— default is visible; a hidden collection is still returned bylistCollections/getCollection; the flag toggles; an update that omitshiddenpreserves it.packages/core/tests/unit/seed/apply.test.ts— the seed flag is applied on create and on re-apply withonConflict: "update".packages/admin/tests/components/Sidebar.test.tsx—visibleCollectionEntriesdrops hidden collections, preserves manifest order, and treats a missing flag as visible.