Skip to content

feat(admin): allow hiding a collection from the admin sidebar - #2264

Open
DavidPivert wants to merge 2 commits into
emdash-cms:mainfrom
DavidPivert:feat/collection-hidden-from-sidebar
Open

feat(admin): allow hiding a collection from the admin sidebar#2264
DavidPivert wants to merge 2 commits into
emdash-cms:mainfrom
DavidPivert:feat/collection-hidden-from-sidebar

Conversation

@DavidPivert

@DavidPivert DavidPivert commented Jul 28, 2026

Copy link
Copy Markdown

What does this PR do?

Adds a hidden flag 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:

  • Migration 054_collection_hidden adds a hidden column to _emdash_collections, NOT NULL DEFAULT 0, guarded by columnExists like 053. Existing collections stay visible.
  • updateCollection preserves the stored value when the input omits hidden, matching the commentsEnabled precedent.
  • The manifest only emits hidden when it is set, so the payload is unchanged for the common visible collection.
  • Sidebar.tsx filters through a new exported pure helper visibleCollectionEntries, 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

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Discussion: #1764 — the Roadmap discussion mirroring #1131. The issue is on the 1.0 milestone, labelled roadmap/1.0 / roadmap/editor-experience, and listed in the 1.0 roadmap tracker.

Scope note: this deliberately leaves out an admin toggle in the Content Type editor to keep the diff reviewable — happy to add it here or in a follow-up if you'd rather the flag be editable from the UI.

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 5 (Claude Code)

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 by listCollections/getCollection; the flag toggles; an update that omits hidden preserves it.
  • packages/core/tests/unit/seed/apply.test.ts — the seed flag is applied on create and on re-apply with onConflict: "update".
  • packages/admin/tests/components/Sidebar.test.tsxvisibleCollectionEntries drops hidden collections, preserves manifest order, and treats a missing flag as visible.
packages/core   vitest run tests/unit/schema/registry.test.ts tests/unit/seed/apply.test.ts   85 passed
packages/core   vitest run tests/database/migrations.test.ts tests/unit/openapi                14 passed
packages/admin  vitest run tests/components/Sidebar.test.tsx                                   18 passed

pnpm --filter @emdash-cms/core typecheck    clean
pnpm --filter @emdash-cms/admin typecheck   clean
oxlint --type-aware --deny-warnings         clean
oxfmt --check && prettier --check .         clean

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-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest 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

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 include hidden, 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 types also omits hidden from its emitted collection shape.
  • The admin client's AdminManifest type doesn't declare hidden on 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:183

    The runtime Collection type now has a required hidden boolean, and the schema handlers return it, but the OpenAPI response schema collectionSchema does 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 commentsEnabled are also missing here, but that gap predates this PR; hidden is the new field that must be added now.)

  • [needs fixing] packages/core/src/astro/routes/api/schema/index.ts:81

    The full schema export endpoint used by the CLI (emdash types) manually builds each collection object and omits the new hidden flag. 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:95

    The admin manifest type used by fetchManifest() does not declare hidden on 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 runtime EmDashManifest.

    			supports: string[];
    			hasSeo: boolean;
    			urlPattern?: string;
    			hidden?: boolean;
    			fields: Record<
    
  • [needs fixing] packages/core/src/emdash-runtime.ts:2365-2366

    This 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 on ManifestCollection.

    Delete lines 2365–2366:

    					hasSeo: collection.hasSeo,
    					urlPattern: collection.urlPattern,
    					...(collection.hidden ? { hidden: true } : {}),
    
  • [needs fixing] packages/admin/tests/components/Sidebar.test.tsx:109

    Test descriptions are comments read by future maintainers; AGENTS.md forbids referencing issues/PRs in comments. Remove the #1131 reference from the describe title.

    	describe("visibleCollectionEntries", () => {
    
  • [needs fixing] packages/core/tests/unit/schema/registry.test.ts:131-160

    These four new test titles reference issue #1131. AGENTS.md says comments must not reference issues, PRs, or review threads. Remove the #1131: prefix from each it(...) 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-191

    These two new test titles reference issue #1131. AGENTS.md says comments must not reference issues, PRs, or review threads. Remove the #1131: prefix from each it(...) 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 () => {
    

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 28, 2026
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>
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review needs-rebase and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(admin): allow hiding user-defined collections from admin sidebar

1 participant