From db4c5cfa924c936ccf634dfb22c53322b8c025b6 Mon Sep 17 00:00:00 2001 From: David Pivert Date: Tue, 28 Jul 2026 17:16:08 +0200 Subject: [PATCH 1/2] feat(admin): allow hiding a collection from the admin sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #1131 Co-Authored-By: Claude Opus 5 --- .changeset/hidden-collections-sidebar.md | 6 +++ docs/src/content/docs/themes/seed-files.mdx | 8 ++++ packages/admin/src/components/Sidebar.tsx | 19 +++++++- packages/admin/src/lib/api/schema.ts | 4 ++ .../admin/tests/components/Sidebar.test.tsx | 25 +++++++++++ packages/core/src/api/schemas/schema.ts | 2 + packages/core/src/astro/types.ts | 6 +++ packages/core/src/cli/commands/export-seed.ts | 1 + .../migrations/054_collection_hidden.ts | 24 ++++++++++ .../core/src/database/migrations/runner.ts | 2 + packages/core/src/database/types.ts | 1 + packages/core/src/emdash-runtime.ts | 3 ++ packages/core/src/schema/registry.ts | 4 ++ packages/core/src/schema/types.ts | 11 +++++ packages/core/src/seed/apply.ts | 2 + packages/core/src/seed/types.ts | 5 +++ .../core/tests/unit/schema/registry.test.ts | 38 ++++++++++++++++ packages/core/tests/unit/seed/apply.test.ts | 45 +++++++++++++++++++ 18 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 .changeset/hidden-collections-sidebar.md create mode 100644 packages/core/src/database/migrations/054_collection_hidden.ts diff --git a/.changeset/hidden-collections-sidebar.md b/.changeset/hidden-collections-sidebar.md new file mode 100644 index 0000000000..ca1cff7d1e --- /dev/null +++ b/.changeset/hidden-collections-sidebar.md @@ -0,0 +1,6 @@ +--- +"@emdash-cms/core": minor +"@emdash-cms/admin": minor +--- + +Adds a `hidden` flag to collections that omits their auto-generated entry from the admin sidebar. Hidden collections keep working everywhere else — REST API, MCP, plugin hooks, and their editor at `/_emdash/admin/content/` — so a plugin that owns a collection end to end can point editors at its own admin UI instead of a raw CRUD list. Set it in a seed file (`"hidden": true`) or via the schema API. diff --git a/docs/src/content/docs/themes/seed-files.mdx b/docs/src/content/docs/themes/seed-files.mdx index 9ce91bf6b5..07d7b27ae6 100644 --- a/docs/src/content/docs/themes/seed-files.mdx +++ b/docs/src/content/docs/themes/seed-files.mdx @@ -121,8 +121,16 @@ Each collection definition creates a content type in the database: | `description` | `string` | No | Admin UI description | | `icon` | `string` | No | Lucide icon name | | `supports` | `array` | No | Features: `"drafts"`, `"revisions"` | +| `hidden` | `boolean`| No | Omit the collection's admin sidebar entry | | `fields` | `array` | Yes | Field definitions | + + ### Field Properties | Property | Type | Required | Description | diff --git a/packages/admin/src/components/Sidebar.tsx b/packages/admin/src/components/Sidebar.tsx index d69ae3607f..52915399bf 100644 --- a/packages/admin/src/components/Sidebar.tsx +++ b/packages/admin/src/components/Sidebar.tsx @@ -57,9 +57,24 @@ export function filterNavItemsByRole( return items.filter((item) => !item.minRole || userRole >= item.minRole); } +/** + * Manifest collections that get an auto-generated sidebar entry, in manifest + * order. Pure function — exported so tests can pin the `hidden` contract + * without rendering the sidebar. + * + * A hidden collection is still shipped in the manifest and stays fully + * routable at `/content/:collection`; it only loses its nav link, so a plugin + * that owns the collection end to end can steer editors to its own admin UI. + */ +export function visibleCollectionEntries( + collections: Record, +): Array<[string, T]> { + return Object.entries(collections).filter(([, config]) => !config.hidden); +} + export interface SidebarNavProps { manifest: { - collections: Record; + collections: Record; plugins: Record< string, { @@ -205,7 +220,7 @@ export function SidebarNav({ manifest }: SidebarNavProps) { const contentItems: NavItem[] = [ { to: "/", label: t`Dashboard`, icon: ADMIN_NAV_ICONS.dashboard }, ]; - for (const [name, config] of Object.entries(manifest.collections)) { + for (const [name, config] of visibleCollectionEntries(manifest.collections)) { contentItems.push({ to: "/content/$collection", label: config.label, diff --git a/packages/admin/src/lib/api/schema.ts b/packages/admin/src/lib/api/schema.ts index 1b991befa4..edf8492512 100644 --- a/packages/admin/src/lib/api/schema.ts +++ b/packages/admin/src/lib/api/schema.ts @@ -36,6 +36,8 @@ export interface SchemaCollection { source?: string; urlPattern?: string; hasSeo: boolean; + /** Sidebar entry omitted in the admin; the collection stays reachable by URL */ + hidden: boolean; commentsEnabled: boolean; commentsModeration: "all" | "first_time" | "none"; commentsClosedAfterDays: number; @@ -83,6 +85,7 @@ export interface CreateCollectionInput { supports?: string[]; urlPattern?: string; hasSeo?: boolean; + hidden?: boolean; } export interface UpdateCollectionInput { @@ -93,6 +96,7 @@ export interface UpdateCollectionInput { supports?: string[]; urlPattern?: string; hasSeo?: boolean; + hidden?: boolean; commentsEnabled?: boolean; commentsModeration?: "all" | "first_time" | "none"; commentsClosedAfterDays?: number; diff --git a/packages/admin/tests/components/Sidebar.test.tsx b/packages/admin/tests/components/Sidebar.test.tsx index 6371ce0f00..4f2646d291 100644 --- a/packages/admin/tests/components/Sidebar.test.tsx +++ b/packages/admin/tests/components/Sidebar.test.tsx @@ -39,6 +39,7 @@ import { resolveNavIcon, resolvePluginPageLabel, toPhosphorIconName, + visibleCollectionEntries, } from "../../src/components/Sidebar"; import { render } from "../utils/render.tsx"; @@ -105,6 +106,30 @@ describe("filterNavItemsByRole", () => { }); }); +describe("visibleCollectionEntries (#1131)", () => { + const collections = { + posts: { label: "Posts" }, + contact_submissions: { label: "Contact Submissions", hidden: true }, + lead_notes: { label: "Lead Notes", hidden: true }, + pages: { label: "Pages", hidden: false }, + }; + + it("drops collections flagged hidden", () => { + expect(visibleCollectionEntries(collections).map(([slug]) => slug)).toEqual(["posts", "pages"]); + }); + + it("keeps manifest order for visible collections", () => { + expect(visibleCollectionEntries({ b: { label: "B" }, a: { label: "A" } })).toEqual([ + ["b", { label: "B" }], + ["a", { label: "A" }], + ]); + }); + + it("treats a missing hidden flag as visible", () => { + expect(visibleCollectionEntries({ posts: { label: "Posts" } })).toHaveLength(1); + }); +}); + describe("resolvePluginPageLabel", () => { // Simulates a plugin that loaded its Lingui catalog into the shared i18n // instance: known msgids translate, unknown ones return the msgid itself diff --git a/packages/core/src/api/schemas/schema.ts b/packages/core/src/api/schemas/schema.ts index f2922289e8..f8649a7433 100644 --- a/packages/core/src/api/schemas/schema.ts +++ b/packages/core/src/api/schemas/schema.ts @@ -86,6 +86,7 @@ export const createCollectionBody = z source: z.string().regex(collectionSourcePattern).optional(), urlPattern: z.string().optional(), hasSeo: z.boolean().optional(), + hidden: z.boolean().optional(), }) .meta({ id: "CreateCollectionBody" }); @@ -98,6 +99,7 @@ export const updateCollectionBody = z supports: z.array(collectionSupportValues).optional(), urlPattern: z.string().nullish(), hasSeo: z.boolean().optional(), + hidden: z.boolean().optional(), commentsEnabled: z.boolean().optional(), commentsModeration: z.enum(["all", "first_time", "none"]).optional(), commentsClosedAfterDays: z.number().int().min(0).optional(), diff --git a/packages/core/src/astro/types.ts b/packages/core/src/astro/types.ts index 042313ece0..bd674df10e 100644 --- a/packages/core/src/astro/types.ts +++ b/packages/core/src/astro/types.ts @@ -31,6 +31,12 @@ export interface ManifestCollection { supports: string[]; hasSeo: boolean; urlPattern?: string; + /** + * Omit the auto-generated sidebar entry in the admin. The collection is + * still listed in the manifest so its routes, editor, and API keep working + * — only the navigation link is dropped. + */ + hidden?: boolean; fields: Record< string, { diff --git a/packages/core/src/cli/commands/export-seed.ts b/packages/core/src/cli/commands/export-seed.ts index 707a8a122c..4bd69bd44c 100644 --- a/packages/core/src/cli/commands/export-seed.ts +++ b/packages/core/src/cli/commands/export-seed.ts @@ -316,6 +316,7 @@ async function exportCollections(db: Kysely): Promise 0 ? collection.supports : undefined, urlPattern: collection.urlPattern || undefined, + hidden: collection.hidden || undefined, fields: fields.map( (field): SeedField => ({ slug: field.slug, diff --git a/packages/core/src/database/migrations/054_collection_hidden.ts b/packages/core/src/database/migrations/054_collection_hidden.ts new file mode 100644 index 0000000000..dfeade4978 --- /dev/null +++ b/packages/core/src/database/migrations/054_collection_hidden.ts @@ -0,0 +1,24 @@ +import type { Kysely } from "kysely"; + +import { columnExists } from "../dialect-helpers.js"; + +/** + * Migration: hide a collection from the admin sidebar. + * + * Adds `hidden` to `_emdash_collections`. A hidden collection stays fully + * functional (REST API, MCP, plugin hooks, direct `/content/:collection` + * URLs) — only its auto-generated sidebar entry is omitted, so plugins that + * own a collection end to end can steer editors to their own admin UI. + */ +export async function up(db: Kysely): Promise { + if (!(await columnExists(db, "_emdash_collections", "hidden"))) { + await db.schema + .alterTable("_emdash_collections") + .addColumn("hidden", "integer", (col) => col.notNull().defaultTo(0)) + .execute(); + } +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable("_emdash_collections").dropColumn("hidden").execute(); +} diff --git a/packages/core/src/database/migrations/runner.ts b/packages/core/src/database/migrations/runner.ts index a3ce955d45..bbc7215672 100644 --- a/packages/core/src/database/migrations/runner.ts +++ b/packages/core/src/database/migrations/runner.ts @@ -56,6 +56,7 @@ import * as m050 from "./050_media_usage_index_status.js"; import * as m051 from "./051_content_taxonomies_denorm.js"; import * as m052 from "./052_media_usage_read_index.js"; import * as m053 from "./053_plugin_mcp_tools.js"; +import * as m054 from "./054_collection_hidden.js"; const MIGRATIONS: Readonly> = Object.freeze({ "001_initial": m001, @@ -110,6 +111,7 @@ const MIGRATIONS: Readonly> = Object.freeze({ "051_content_taxonomies_denorm": m051, "052_media_usage_read_index": m052, "053_plugin_mcp_tools": m053, + "054_collection_hidden": m054, }); /** Total number of registered migrations. Exported for use in tests. */ diff --git a/packages/core/src/database/types.ts b/packages/core/src/database/types.ts index 0f4ba13ed7..05c6dd8543 100644 --- a/packages/core/src/database/types.ts +++ b/packages/core/src/database/types.ts @@ -296,6 +296,7 @@ export interface CollectionTable { search_config: string | null; // JSON: { enabled: boolean, weights: Record } has_seo: number; // 0 or 1 — opt-in SEO fields for this collection url_pattern: string | null; // URL pattern with {slug} placeholder (e.g. "/blog/{slug}") + hidden: Generated; // 0 or 1 — omit the auto-generated admin sidebar entry comments_enabled: Generated; // 0 or 1 comments_moderation: Generated; // 'all' | 'first_time' | 'none' comments_closed_after_days: Generated; // 0 = never close diff --git a/packages/core/src/emdash-runtime.ts b/packages/core/src/emdash-runtime.ts index 5e4a247255..129d566306 100644 --- a/packages/core/src/emdash-runtime.ts +++ b/packages/core/src/emdash-runtime.ts @@ -2362,6 +2362,9 @@ export class EmDashRuntime { supports: collection.supports || [], hasSeo: collection.hasSeo, urlPattern: collection.urlPattern, + // Only emitted when set, so the manifest payload is unchanged + // for the overwhelmingly common visible collection. + ...(collection.hidden ? { hidden: true } : {}), fields, }; } diff --git a/packages/core/src/schema/registry.ts b/packages/core/src/schema/registry.ts index a2608f45a2..9cd58d2e4c 100644 --- a/packages/core/src/schema/registry.ts +++ b/packages/core/src/schema/registry.ts @@ -256,6 +256,7 @@ export class SchemaRegistry { supports: JSON.stringify(supports), source: input.source ?? "manual", has_seo: hasSeo ? 1 : 0, + hidden: input.hidden ? 1 : 0, comments_enabled: input.commentsEnabled ? 1 : 0, url_pattern: input.urlPattern ?? null, }) @@ -354,6 +355,7 @@ export class SchemaRegistry { supports: JSON.stringify(supports), source: "seed", has_seo: hasSeo ? 1 : 0, + hidden: input.hidden ? 1 : 0, comments_enabled: input.commentsEnabled ? 1 : 0, url_pattern: input.urlPattern ?? null, }) @@ -416,6 +418,7 @@ export class SchemaRegistry { ? (input.urlPattern ?? null) : (existing.urlPattern ?? null), has_seo: hasSeo ? 1 : 0, + hidden: input.hidden !== undefined ? (input.hidden ? 1 : 0) : existing.hidden ? 1 : 0, comments_enabled: input.commentsEnabled !== undefined ? input.commentsEnabled @@ -1228,6 +1231,7 @@ export class SchemaRegistry { source: row.source && isCollectionSource(row.source) ? row.source : undefined, hasSeo: row.has_seo === 1, urlPattern: row.url_pattern ?? undefined, + hidden: row.hidden === 1, commentsEnabled: row.comments_enabled === 1, commentsModeration: moderation === "all" || moderation === "first_time" || moderation === "none" diff --git a/packages/core/src/schema/types.ts b/packages/core/src/schema/types.ts index 6d5055e981..ddff953c27 100644 --- a/packages/core/src/schema/types.ts +++ b/packages/core/src/schema/types.ts @@ -171,6 +171,13 @@ export interface Collection { hasSeo: boolean; /** URL pattern with {slug} placeholder (e.g. "/{slug}", "/blog/{slug}") */ urlPattern?: string; + /** + * Omit this collection's auto-generated entry from the admin sidebar. + * The collection stays fully functional everywhere else (API, MCP, hooks, + * direct `/content/:collection` URLs) — this only hides the nav link, so a + * plugin that owns the collection can point editors at its own admin UI. + */ + hidden: boolean; /** Whether comments are enabled for this collection */ commentsEnabled: boolean; /** Moderation strategy: "all" | "first_time" | "none" */ @@ -219,6 +226,8 @@ export interface CreateCollectionInput { source?: CollectionSource; urlPattern?: string; hasSeo?: boolean; + /** Omit the auto-generated admin sidebar entry (defaults to false) */ + hidden?: boolean; commentsEnabled?: boolean; } @@ -233,6 +242,8 @@ export interface UpdateCollectionInput { supports?: CollectionSupport[]; urlPattern?: string; hasSeo?: boolean; + /** Omit the auto-generated admin sidebar entry */ + hidden?: boolean; commentsEnabled?: boolean; commentsModeration?: "all" | "first_time" | "none"; commentsClosedAfterDays?: number; diff --git a/packages/core/src/seed/apply.ts b/packages/core/src/seed/apply.ts index 6c1da34284..1c9c1766b7 100644 --- a/packages/core/src/seed/apply.ts +++ b/packages/core/src/seed/apply.ts @@ -179,6 +179,7 @@ export async function applySeed( icon: collection.icon, supports: collection.supports || [], urlPattern: collection.urlPattern, + hidden: collection.hidden, commentsEnabled: collection.commentsEnabled, }); result.collections.updated++; @@ -247,6 +248,7 @@ export async function applySeed( icon: collection.icon, supports: collection.supports || [], urlPattern: collection.urlPattern, + hidden: collection.hidden, commentsEnabled: collection.commentsEnabled, }, fields, diff --git a/packages/core/src/seed/types.ts b/packages/core/src/seed/types.ts index a106bfcd51..1bcac6324e 100644 --- a/packages/core/src/seed/types.ts +++ b/packages/core/src/seed/types.ts @@ -74,6 +74,11 @@ export interface SeedCollection { icon?: string; supports?: ("drafts" | "revisions" | "preview" | "scheduling" | "search" | "seo")[]; urlPattern?: string; + /** + * Omit this collection from the admin sidebar. It stays reachable through + * the API, MCP, plugin hooks, and direct `/content/:collection` URLs. + */ + hidden?: boolean; /** Enable comments on this collection */ commentsEnabled?: boolean; fields: SeedField[]; diff --git a/packages/core/tests/unit/schema/registry.test.ts b/packages/core/tests/unit/schema/registry.test.ts index 0991d0d9ce..35b6de4af9 100644 --- a/packages/core/tests/unit/schema/registry.test.ts +++ b/packages/core/tests/unit/schema/registry.test.ts @@ -128,6 +128,44 @@ describe("SchemaRegistry", () => { expect(updated.supports).toEqual(["drafts"]); }); + it("#1131: collections are visible in the sidebar by default", async () => { + const collection = await registry.createCollection({ slug: "posts", label: "Posts" }); + + expect(collection.hidden).toBe(false); + }); + + it("#1131: creates a collection hidden from the sidebar", async () => { + const collection = await registry.createCollection({ + slug: "contact_submissions", + label: "Contact Submissions", + hidden: true, + }); + + expect(collection.hidden).toBe(true); + // A hidden collection is only hidden from the sidebar — it must still + // be listed by the registry so its routes, editor, API, and MCP tools + // keep resolving. + const listed = await registry.listCollections(); + expect(listed.map((c) => c.slug)).toContain("contact_submissions"); + expect(await registry.getCollection("contact_submissions")).not.toBeNull(); + }); + + it("#1131: toggles hidden on an existing collection", async () => { + await registry.createCollection({ slug: "posts", label: "Posts" }); + + expect((await registry.updateCollection("posts", { hidden: true })).hidden).toBe(true); + expect((await registry.updateCollection("posts", { hidden: false })).hidden).toBe(false); + }); + + it("#1131: preserves hidden when an update omits it", async () => { + await registry.createCollection({ slug: "posts", label: "Posts", hidden: true }); + + const updated = await registry.updateCollection("posts", { label: "Blog Posts" }); + + expect(updated.label).toBe("Blog Posts"); + expect(updated.hidden).toBe(true); + }); + it("should throw when updating non-existent collection", async () => { await expect(registry.updateCollection("nonexistent", { label: "Test" })).rejects.toThrow( SchemaError, diff --git a/packages/core/tests/unit/seed/apply.test.ts b/packages/core/tests/unit/seed/apply.test.ts index 6b82987143..7ff4f92516 100644 --- a/packages/core/tests/unit/seed/apply.test.ts +++ b/packages/core/tests/unit/seed/apply.test.ts @@ -163,6 +163,51 @@ describe("applySeed", () => { expect(row.rows[0]?.title).toBe("Untitled"); }); + it("#1131: applies the hidden flag from the seed", async () => { + const seed: SeedFile = { + version: "1", + collections: [ + { + slug: "contact_submissions", + label: "Contact Submissions", + hidden: true, + fields: [{ slug: "title", label: "Title", type: "string" }], + }, + { + slug: "posts", + label: "Posts", + fields: [{ slug: "title", label: "Title", type: "string" }], + }, + ], + }; + + await applySeed(db, seed); + + const registry = new SchemaRegistry(db); + expect((await registry.getCollection("contact_submissions"))?.hidden).toBe(true); + expect((await registry.getCollection("posts"))?.hidden).toBe(false); + }); + + it("#1131: updates the hidden flag when re-applying with onConflict update", async () => { + const collection = { + slug: "contact_submissions", + label: "Contact Submissions", + fields: [{ slug: "title", label: "Title", type: "string" as const }], + }; + await applySeed(db, { version: "1", collections: [collection] }); + + await applySeed( + db, + { version: "1", collections: [{ ...collection, hidden: true }] }, + { + onConflict: "update", + }, + ); + + const registry = new SchemaRegistry(db); + expect((await registry.getCollection("contact_submissions"))?.hidden).toBe(true); + }); + it("should skip existing collections", async () => { // Create collection first const registry = new SchemaRegistry(db); From 0a15358f41a59580bf62e71958b74463f32ee460 Mon Sep 17 00:00:00 2001 From: David Pivert Date: Wed, 29 Jul 2026 09:57:10 +0200 Subject: [PATCH 2/2] fix(admin): expose hidden on the schema API and manifest contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packages/admin/src/lib/api/client.ts | 1 + packages/admin/tests/components/Sidebar.test.tsx | 2 +- packages/core/src/api/schemas/schema.ts | 1 + packages/core/src/astro/routes/api/schema/index.ts | 1 + packages/core/src/emdash-runtime.ts | 2 -- packages/core/tests/unit/schema/registry.test.ts | 8 ++++---- packages/core/tests/unit/seed/apply.test.ts | 4 ++-- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/admin/src/lib/api/client.ts b/packages/admin/src/lib/api/client.ts index 9b0ff18ef2..2d49de3f19 100644 --- a/packages/admin/src/lib/api/client.ts +++ b/packages/admin/src/lib/api/client.ts @@ -93,6 +93,7 @@ export interface AdminManifest { supports: string[]; hasSeo: boolean; urlPattern?: string; + hidden?: boolean; fields: Record< string, { diff --git a/packages/admin/tests/components/Sidebar.test.tsx b/packages/admin/tests/components/Sidebar.test.tsx index 4f2646d291..12f11449de 100644 --- a/packages/admin/tests/components/Sidebar.test.tsx +++ b/packages/admin/tests/components/Sidebar.test.tsx @@ -106,7 +106,7 @@ describe("filterNavItemsByRole", () => { }); }); -describe("visibleCollectionEntries (#1131)", () => { +describe("visibleCollectionEntries", () => { const collections = { posts: { label: "Posts" }, contact_submissions: { label: "Contact Submissions", hidden: true }, diff --git a/packages/core/src/api/schemas/schema.ts b/packages/core/src/api/schemas/schema.ts index f8649a7433..c093f982b7 100644 --- a/packages/core/src/api/schemas/schema.ts +++ b/packages/core/src/api/schemas/schema.ts @@ -181,6 +181,7 @@ export const collectionSchema = z source: z.string().nullable(), urlPattern: z.string().nullable(), hasSeo: z.boolean(), + hidden: z.boolean(), createdAt: z.string(), updatedAt: z.string(), }) diff --git a/packages/core/src/astro/routes/api/schema/index.ts b/packages/core/src/astro/routes/api/schema/index.ts index ddadd34378..3ab947cad3 100644 --- a/packages/core/src/astro/routes/api/schema/index.ts +++ b/packages/core/src/astro/routes/api/schema/index.ts @@ -79,6 +79,7 @@ import type { PortableTextBlock } from "emdash"; description: c.description, icon: c.icon, supports: c.supports, + hidden: c.hidden, fields: c.fields.map((f) => ({ slug: f.slug, label: f.label, diff --git a/packages/core/src/emdash-runtime.ts b/packages/core/src/emdash-runtime.ts index 129d566306..ba94114073 100644 --- a/packages/core/src/emdash-runtime.ts +++ b/packages/core/src/emdash-runtime.ts @@ -2362,8 +2362,6 @@ export class EmDashRuntime { supports: collection.supports || [], hasSeo: collection.hasSeo, urlPattern: collection.urlPattern, - // Only emitted when set, so the manifest payload is unchanged - // for the overwhelmingly common visible collection. ...(collection.hidden ? { hidden: true } : {}), fields, }; diff --git a/packages/core/tests/unit/schema/registry.test.ts b/packages/core/tests/unit/schema/registry.test.ts index 35b6de4af9..d04f198973 100644 --- a/packages/core/tests/unit/schema/registry.test.ts +++ b/packages/core/tests/unit/schema/registry.test.ts @@ -128,13 +128,13 @@ describe("SchemaRegistry", () => { expect(updated.supports).toEqual(["drafts"]); }); - it("#1131: collections are visible in the sidebar by default", async () => { + it("collections are visible in the sidebar by default", async () => { const collection = await registry.createCollection({ slug: "posts", label: "Posts" }); expect(collection.hidden).toBe(false); }); - it("#1131: creates a collection hidden from the sidebar", async () => { + it("creates a collection hidden from the sidebar", async () => { const collection = await registry.createCollection({ slug: "contact_submissions", label: "Contact Submissions", @@ -150,14 +150,14 @@ describe("SchemaRegistry", () => { expect(await registry.getCollection("contact_submissions")).not.toBeNull(); }); - it("#1131: toggles hidden on an existing collection", async () => { + it("toggles hidden on an existing collection", async () => { await registry.createCollection({ slug: "posts", label: "Posts" }); expect((await registry.updateCollection("posts", { hidden: true })).hidden).toBe(true); expect((await registry.updateCollection("posts", { hidden: false })).hidden).toBe(false); }); - it("#1131: preserves hidden when an update omits it", async () => { + it("preserves hidden when an update omits it", async () => { await registry.createCollection({ slug: "posts", label: "Posts", hidden: true }); const updated = await registry.updateCollection("posts", { label: "Blog Posts" }); diff --git a/packages/core/tests/unit/seed/apply.test.ts b/packages/core/tests/unit/seed/apply.test.ts index 7ff4f92516..b783d1fcb5 100644 --- a/packages/core/tests/unit/seed/apply.test.ts +++ b/packages/core/tests/unit/seed/apply.test.ts @@ -163,7 +163,7 @@ describe("applySeed", () => { expect(row.rows[0]?.title).toBe("Untitled"); }); - it("#1131: applies the hidden flag from the seed", async () => { + it("applies the hidden flag from the seed", async () => { const seed: SeedFile = { version: "1", collections: [ @@ -188,7 +188,7 @@ describe("applySeed", () => { expect((await registry.getCollection("posts"))?.hidden).toBe(false); }); - it("#1131: updates the hidden flag when re-applying with onConflict update", async () => { + it("updates the hidden flag when re-applying with onConflict update", async () => { const collection = { slug: "contact_submissions", label: "Contact Submissions",