diff --git a/.changeset/dashboard-content-card-hidden-collections.md b/.changeset/dashboard-content-card-hidden-collections.md new file mode 100644 index 0000000000..17401f917c --- /dev/null +++ b/.changeset/dashboard-content-card-hidden-collections.md @@ -0,0 +1,5 @@ +--- +"@emdash-cms/admin": patch +--- + +Fixes the dashboard's Content card listing collections that are hidden from navigation. The card now matches the sidebar and the quick actions; hidden collections still count toward the dashboard totals, still appear in Recent Activity, and stay reachable by URL, API, and plugins. diff --git a/docs/src/content/docs/concepts/collections.mdx b/docs/src/content/docs/concepts/collections.mdx index eafb77a975..7492d6cc04 100644 --- a/docs/src/content/docs/concepts/collections.mdx +++ b/docs/src/content/docs/concepts/collections.mdx @@ -50,10 +50,10 @@ Collection settings control how editors and public pages use the entries: - **Comments** can be enabled per collection, with moderation and automatic closing settings. - **Icon** names the Phosphor icon shown next to the collection in the sidebar and command palette, such as `calendar-blank`; a name that does not resolve falls back to the default icon. -- **Hide from navigation** removes the sidebar entry, the command palette link, and the dashboard - quick action while the collection stays reachable by URL, API, and plugins. Turning off **Quick action on the - dashboard** removes only the quick action, for collections that plugins or imports fill rather - than editors. +- **Hide from navigation** removes the sidebar entry, the command palette link, the dashboard quick + action, and the dashboard **Content** card row while the collection stays reachable by URL, API, + and plugins. Turning off **Quick action on the dashboard** removes only the quick action, for + collections that plugins or imports fill rather than editors. - **Group** places the collection in a collapsible sidebar folder. Collections with the same group share one folder, positioned where the first of them would appear. A taxonomy joins the folder when every collection it is assigned to is shown in that folder. diff --git a/docs/src/content/docs/themes/seed-files.mdx b/docs/src/content/docs/themes/seed-files.mdx index 50706a776e..1ef20b4f13 100644 --- a/docs/src/content/docs/themes/seed-files.mdx +++ b/docs/src/content/docs/themes/seed-files.mdx @@ -196,7 +196,7 @@ A collection requires `slug`, `label`, and `fields`: | `supports` | `string[]` | Any of `drafts`, `revisions`, `preview`, `scheduling`, `search`, and `seo` | | `urlPattern` | `string` | Public pattern such as `/posts/{slug}`, with at most one placeholder per path segment | | `routable` | `boolean` | Whether published entries require a slug; defaults to `true` | -| `hidden` | `boolean` | Hides the generated sidebar link, command palette link, and dashboard quick action; the collection stays reachable by URL and API | +| `hidden` | `boolean` | Hides the generated sidebar link, command palette link, dashboard quick action, and dashboard **Content** card row; the collection stays reachable by URL and API | | `sortOrder` | `number` | Explicit admin-sidebar position; ordered collections come first, ascending | | `group` | `string` | Admin-sidebar folder; collections with the same group share one collapsible entry | | `commentsEnabled` | `boolean` | Enables comments for the collection | diff --git a/packages/admin/src/components/ContentTypeEditor.tsx b/packages/admin/src/components/ContentTypeEditor.tsx index 6b6e13b288..cdc89c1cc3 100644 --- a/packages/admin/src/components/ContentTypeEditor.tsx +++ b/packages/admin/src/components/ContentTypeEditor.tsx @@ -592,7 +592,7 @@ export function ContentTypeEditor({
{t`Hide from navigation`}

- {t`Removes the sidebar entry, command palette link, and dashboard quick action; entries stay reachable by URL, API, and plugins`} + {t`Removes the sidebar entry, command palette link, dashboard quick action, and dashboard Content card row; entries stay reachable by URL, API, and plugins`}

} diff --git a/packages/admin/src/components/Dashboard.tsx b/packages/admin/src/components/Dashboard.tsx index d34bd3854b..0b3ed62a6e 100644 --- a/packages/admin/src/components/Dashboard.tsx +++ b/packages/admin/src/components/Dashboard.tsx @@ -492,6 +492,7 @@ function CollectionList({ loading: boolean; }) { const { t } = useLingui(); + const visibleCollections = collections.filter((col) => !manifest.collections[col.slug]?.hidden); return ( @@ -499,13 +500,13 @@ function CollectionList({ {loading ? ( - ) : collections.length === 0 ? ( + ) : visibleCollections.length === 0 ? (

{t`No collections configured`}

) : (
- {collections.map((col) => { + {visibleCollections.map((col) => { const config = manifest.collections[col.slug]; return ( { await expect.element(screen.getByRole("link", { name: "Setting" })).not.toBeInTheDocument(); }); + it("omits hidden collections from the Content card", async () => { + mockFetchDashboardStats.mockResolvedValue( + makeStats([ + { slug: "pages", label: "Pages", total: 2, published: 1, draft: 1, scheduled: 0 }, + { slug: "sync_runs", label: "Sync runs", total: 5, published: 5, draft: 0, scheduled: 0 }, + ]), + ); + const withHidden: AdminManifest = { + ...manifest, + collections: { + ...manifest.collections, + sync_runs: { + ...manifest.collections.pages!, + label: "Sync runs", + labelSingular: "Sync run", + hidden: true, + }, + }, + }; + + const screen = await render(); + + await expect + .element(screen.getByRole("link", { name: /^Pages/ })) + .toHaveAttribute("href", "/content/pages"); + await expect.element(screen.getByRole("link", { name: /Sync runs/ })).not.toBeInTheDocument(); + expect(screen.container.querySelector('a[href="/content/sync_runs"]')).toBeNull(); + }); + it("uses the same heading level for every dashboard card title", async () => { mockFetchDashboardStats.mockResolvedValue(makeStats([])); diff --git a/packages/core/src/astro/types.ts b/packages/core/src/astro/types.ts index 6d3eacc307..0170011531 100644 --- a/packages/core/src/astro/types.ts +++ b/packages/core/src/astro/types.ts @@ -49,9 +49,9 @@ export interface ManifestCollection { titleField?: string; dateField?: string; /** - * Omit the auto-generated sidebar entry and dashboard quick action in the - * admin. The collection is still listed in the manifest so its routes, - * editor, and API keep working. + * Omit the auto-generated sidebar entry, dashboard quick action, and + * dashboard Content card row in the admin. The collection is still listed + * in the manifest so its routes, editor, and API keep working. */ hidden?: boolean; /** Phosphor icon name for the sidebar entry (kebab-case, e.g. `calendar-blank`). */ diff --git a/packages/core/src/database/types.ts b/packages/core/src/database/types.ts index cdbabe910a..3187f5d9da 100644 --- a/packages/core/src/database/types.ts +++ b/packages/core/src/database/types.ts @@ -448,7 +448,7 @@ export interface CollectionTable { date_field: string | null; // field slug (datetime) for the admin list Date column (NULL = default) url_pattern: string | null; // URL pattern with {slug} placeholder (e.g. "/blog/{slug}") routable: Generated; // 0 or 1 — published entries require a slug when enabled - hidden: Generated; // 0 or 1 — omit the auto-generated sidebar entry and dashboard quick action + hidden: Generated; // 0 or 1 — omit the auto-generated sidebar entry, dashboard quick action, and dashboard Content card row sort_order: number | null; // explicit admin sidebar position; NULL = alphabetical fallback nav_group: string | null; // admin sidebar folder label; NULL = inline comments_enabled: Generated; // 0 or 1 diff --git a/packages/core/src/schema/types.ts b/packages/core/src/schema/types.ts index 0c2f44156c..eb78f27569 100644 --- a/packages/core/src/schema/types.ts +++ b/packages/core/src/schema/types.ts @@ -282,10 +282,11 @@ export interface Collection { /** Whether published entries require a public slug. Defaults to true. */ routable?: boolean; /** - * Omit this collection's auto-generated sidebar entry and dashboard quick - * action. The collection stays fully functional everywhere else (API, MCP, - * hooks, direct `/content/:collection` URLs), so a plugin that owns the - * collection can point editors at its own admin UI. + * Omit this collection's auto-generated sidebar entry, dashboard quick + * action, and dashboard Content card row. The collection stays fully + * functional everywhere else (API, MCP, hooks, direct `/content/:collection` + * URLs), so a plugin that owns the collection can point editors at its own + * admin UI. */ hidden: boolean; /** @@ -358,7 +359,7 @@ export interface CreateCollectionInput { urlPattern?: string; routable?: boolean; hasSeo?: boolean; - /** Omit the auto-generated sidebar entry and dashboard quick action (defaults to false) */ + /** Omit the auto-generated sidebar entry, dashboard quick action, and dashboard Content card row (defaults to false) */ hidden?: boolean; /** Explicit admin sidebar position (omit for the alphabetical fallback) */ sortOrder?: number | null; @@ -382,7 +383,7 @@ export interface UpdateCollectionInput { urlPattern?: string | null; routable?: boolean; hasSeo?: boolean; - /** Omit the auto-generated sidebar entry and dashboard quick action */ + /** Omit the auto-generated sidebar entry, dashboard quick action, and dashboard Content card row */ hidden?: boolean; /** Explicit admin sidebar position; `null` clears it back to alphabetical */ sortOrder?: number | null; diff --git a/packages/core/src/seed/types.ts b/packages/core/src/seed/types.ts index 61120ee4e4..9a4e338fb0 100644 --- a/packages/core/src/seed/types.ts +++ b/packages/core/src/seed/types.ts @@ -98,9 +98,9 @@ export interface SeedCollection { /** Require a slug before an entry can be published. Defaults to true. */ routable?: boolean; /** - * Omit this collection from the admin sidebar and the dashboard quick - * actions. It stays reachable through the API, MCP, plugin hooks, and - * direct `/content/:collection` URLs. + * Omit this collection from the admin sidebar, the dashboard quick + * actions, and the dashboard Content card. It stays reachable through the + * API, MCP, plugin hooks, and direct `/content/:collection` URLs. */ hidden?: boolean; /**