Skip to content

feat(admin): sidebar menu tree with collection grouping, plugin subgroups, and public menu sync#1024

Open
ahliweb wants to merge 20 commits into
emdash-cms:mainfrom
ahliweb:sidebar-menu-tree
Open

feat(admin): sidebar menu tree with collection grouping, plugin subgroups, and public menu sync#1024
ahliweb wants to merge 20 commits into
emdash-cms:mainfrom
ahliweb:sidebar-menu-tree

Conversation

@ahliweb
Copy link
Copy Markdown
Contributor

@ahliweb ahliweb commented May 14, 2026

What does this PR do?

Implements a comprehensive admin sidebar redesign that adds collection grouping, plugin subgroups, nested submenus, icon rendering, sidebar configuration, and bidirectional sync with public menus.

Discussion: #1027

Data Layer

  • Migration 038: Adds sort_order (INTEGER, default 0) and group (TEXT, nullable) columns to _emdash_collections
  • Collection types: sortOrder, group, and icon added to Collection, CreateCollectionInput, UpdateCollectionInput, ManifestCollection
  • Plugin types: group and sortOrder added to PluginAdminPage and ManifestPlugin.adminPages
  • Ordering: Collections now ordered by sort_order ASC, slug ASC instead of just slug ASC

Admin Sidebar Rendering

  • Collection grouping: Collections with a group field render in separate collapsible sidebar groups; ungrouped collections remain in the default "Content" group
  • Plugin grouping: Plugin pages with a group field render in separate collapsible sidebar groups; ungrouped pages remain in "Plugins"
  • Icon resolution: resolveIcon() helper maps 30+ icon names to Phosphor components with fallback to defaults
  • Nested submenus: NavItem.children support with NavSubMenu expandable component (max 1 level of nesting)
  • Sidebar config: hideCoreFeatures and hideCollections options in EmDashConfig.sidebar to hide items from the nav
  • RTL-safe: All layout uses logical Tailwind classes (ms-*, ps-*, start-*); chevrons flip in RTL

Menu Sync

  • addToMenu: New field on CreateCollectionInput — auto-adds a type: "collection" menu item to the specified public menu on collection creation
  • Auto-cleanup: Deleting a collection automatically removes its menu items
  • Manual sync endpoint: POST /_emdash/api/schema/collections/:slug/sync-menu to add a collection to a menu after creation
  • Full sync engine:
    • GET /_emdash/api/menus/:name/sync-diff — preview changes (toAdd, toRemove, toReorder)
    • POST /_emdash/api/menus/:name/sync — apply changes to align menu with sidebar structure

Reordering UI

  • CollectionReorderDialog: Drag-and-drop dialog using @dnd-kit/core + @dnd-kit/sortable for reordering collections
  • API endpoint: POST /_emdash/api/schema/collections/reorder — batch updates sort_order for multiple collections
  • Integration: "Reorder" button added to Content Types admin page

What changed

  • packages/core/src/database/migrations/038_collection_grouping.ts (new)
  • packages/core/src/schema/types.tssortOrder, group, icon, addToMenu fields
  • packages/core/src/schema/registry.tsreorderCollections, ordering in listCollections
  • packages/core/src/astro/types.tsManifestCollection gains sortOrder, group, icon
  • packages/core/src/astro/integration/runtime.tssidebar config option, PluginAdminPage fields
  • packages/core/src/emdash-runtime.ts — passes sortOrder, group, icon to manifest
  • packages/core/src/api/handlers/schema.tssyncCollectionToMenu, removeCollectionFromMenu, handleSchemaCollectionMenuSync
  • packages/core/src/api/handlers/menu-sync.ts (new) — computeMenuSyncDiff, applyMenuSyncDiff, syncSidebarToMenu
  • packages/core/src/astro/routes/api/schema/collections/reorder.ts (new)
  • packages/core/src/astro/routes/api/schema/collections/[slug]/sync-menu.ts (new)
  • packages/core/src/astro/routes/api/menus/[name]/sync.ts (new)
  • packages/admin/src/components/Sidebar.tsx — rewritten for grouping, icons, submenus, hiding
  • packages/admin/src/components/CollectionReorderDialog.tsx (new)
  • packages/admin/src/components/ContentTypeList.tsx — reorder button + dialog integration
  • packages/admin/src/lib/api/schema.tsreorderCollections, addToMenu field
  • packages/admin/src/lib/api/menus.tsfetchMenuSyncDiff, applyMenuSync
  • packages/core/tests/unit/schema/collection-ordering.test.ts (new) — 11 tests
  • packages/core/tests/unit/menus/menu-sync.test.ts (new) — 12 tests
  • packages/core/tests/unit/menus/menu-sync-exports.test.ts (new) — 5 tests

Type of change

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.7 (via opencode)

Sidebar Rendering Structure

Dashboard
---
Content (ungrouped collections + Media)
---
Blog (named collection group)
  ├─ Posts
  └─ Authors
---
Shop (named collection group)
  ├─ Products
  └─ Categories
---
Manage (filterable by hideCoreFeatures)
  ├─ Comments
  ├─ Menus
  │   ├─ Header  ← nested submenu
  │   └─ Footer
  └─ ...
---
Admin (filterable by hideCoreFeatures)
---
SEO (named plugin group)
  ├─ Sitemap
  └─ Meta Tags
---
Plugins (ungrouped plugin pages)

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: 5fd85ba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@emdash-cms/admin Minor
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

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

@github-actions
Copy link
Copy Markdown
Contributor

Scope check

This PR changes 1,743 lines across 23 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 14, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1024

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1024

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1024

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1024

emdash

npm i https://pkg.pr.new/emdash@1024

create-emdash

npm i https://pkg.pr.new/create-emdash@1024

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1024

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1024

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1024

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1024

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1024

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1024

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1024

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1024

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1024

commit: 5fd85ba

@ahliweb ahliweb force-pushed the sidebar-menu-tree branch from 2834b10 to 92306a5 Compare May 14, 2026 06:27
emdashbot Bot and others added 4 commits May 14, 2026 06:28
Fixes typecheck errors in menu-sync.ts and registry.ts where
sort_order was not recognized on _emdash_collections.
…pping

- Add sortOrder and group columns to Collection interface
- Add sortOrder and group to UpdateCollectionInput
- Map sort_order and group in mapCollectionRow
- Include sortOrder/group in createCollection and updateCollection
- Order listCollections by sort_order ASC, slug ASC
- Register migration 039_collection_grouping
@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.

@ahliweb
Copy link
Copy Markdown
Contributor Author

ahliweb commented May 14, 2026

Tracking issue: #1038

@ascorbic
Copy link
Copy Markdown
Collaborator

/review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Solid feature scope with good test coverage. Found a handful of correctness issues worth addressing before merge, the most important being a duplicate "Media" link in every named collection group, and several i18n / locale-correctness gaps in the menu-sync engine.

Doc nit: the PR body refers to "Migration 038" but the actual file is 039_collection_grouping.ts.

One note that doesn't fit on a diff line: listCollectionsWithFields was not updated to order by sort_order the way listCollections was. listCollectionsWithFields is the method the manifest build uses (emdash-runtime.ts:1359). The admin sidebar happens to be unaffected because the manifest stores collections as a Record<slug, …> and the client re-sorts by sortOrder, but any other caller still sees alphabetical order, and the divergence between the two list methods is a maintenance trap. Add orderBy("sort_order", "asc") before the slug ordering there too. Related: the collection-ordering.test.ts > listCollectionsWithFields ordering test passes only by coincidence — its inputs sort the same alphabetically and by sort_order. Pick inputs where the two disagree (e.g. zebra(0), art(1)) to actually catch the missing orderBy.

.map(
([group, items]): NavGroup => ({
label: group,
items: [...items, { to: "/media", label: t`Media`, icon: Image }],
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.

Bug: Media is duplicated in every named collection group.

The ungrouped collections branch (line 393) already adds Media once into the default "Content" group, and this block appends Media again to every named collection group. A site with named groups "Blog" and "Shop" will render three Media links (one under Content, one under Blog, one under Shop).

Suggested change
items: [...items, { to: "/media", label: t`Media`, icon: Image }],
items,

Then drop the spread + extra item entirely so Media only lives in the ungrouped "Content" group.

const menu = await db
.selectFrom("_emdash_menus")
.select(["id", "locale"])
.where("name", "=", diff.toAdd[0]?.menuName ?? "")
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.

applyMenuSyncDiff derives the target menu from diff.toAdd[0]?.menuName — when toAdd is empty (a sync that only needs removals/reorders), this falls back to "", the menu lookup returns nothing, and the rest of the function operates without ever knowing which menu it is mutating. toRemove / toReorder happen to work because they look items up by ID, but the contract is broken: the function silently does the right thing only by coincidence, and a sync-by-name where the menu doesn't exist returns MENU_NOT_FOUND only when there are additions to apply.

Consider taking menuName as a parameter to applyMenuSyncDiff (it already comes from the caller in syncSidebarToMenu), validating the menu up front, and scoping toReorder / toRemove to that menu's items so a malformed diff can't reorder/delete items from other menus.

.selectFrom("_emdash_menu_items")
.select(["_emdash_menu_items.id", "reference_collection", "sort_order"])
.innerJoin("_emdash_menus", "_emdash_menu_items.menu_id", "_emdash_menus.id")
.where("_emdash_menus.name", "=", menuName)
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.

Locale-unaware menu lookup. Migration 036_i18n_menus_and_taxonomies made _emdash_menus keyed by UNIQUE(name, locale), so on multi-locale installs there can be multiple menus named e.g. "primary". This query (and the corresponding insert/delete in syncCollectionToMenu) matches all of them indiscriminately — computeMenuSyncDiff will mix items from every locale's menu into one diff, applyMenuSyncDiff will only add items to the first match, and syncCollectionToMenu will pick whichever menu executeTakeFirst() happens to return.

At minimum filter by locale (default to the site's default locale) or accept a locale parameter; otherwise document explicitly that menu-sync is only safe on single-locale installs.

.where("id", "=", item.id)
.execute();
reordered++;
}
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.

applyMenuSyncDiff runs add / remove / reorder as separate statements with no transaction. If any one fails midway (e.g. a unique-constraint violation on insert, or a D1 hiccup on the third update), the menu is left in a half-applied state and the caller gets a generic SYNC_APPLY_ERROR with no indication of what landed. Wrap the three loops in withTransaction (already used elsewhere in this codebase) so the apply is all-or-nothing.

success: true,
data: { toAdd, toRemove, toReorder },
};
} catch {
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.

Bare catch {} swallows all errors and returns a generic message. Per AGENTS.md ("API Routes: Use Shared Utilities"), catch the error binding and log internally so the cause isn't lost — same for the second catch {} at line 173 and the silent catch {} blocks added in schema.ts (syncCollectionToMenu, removeCollectionFromMenu, handleSchemaCollectionMenuSync).

const rows = await this.db
.selectFrom("_emdash_collections")
.selectAll()
.orderBy("sort_order", "asc")
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.

Note: listCollections is now ordered by sort_order, but its sibling listCollectionsWithFields (line 168, not shown in this diff) was not updated. That second method is the one the manifest build uses. See review summary for details.

Comment thread packages/core/src/schema/registry.ts Outdated
for (const { slug, sortOrder } of collections) {
await this.db
.updateTable("_emdash_collections")
.set({ sort_order: sortOrder })
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.

reorderCollections runs an existence check per slug (N queries) followed by an update per slug (N more queries) — 2N round-trips on a route that's already paying a D1 primary write per UPDATE. On a 50-collection reorder that's 100 round-trips when one SELECT slug WHERE slug IN (...) for validation, plus the N updates, would do the same correctness check in N+1.

Also: the reorder isn't wrapped in withTransaction, so a failure mid-batch leaves collections in an inconsistent order.

* Expandable nav item with children (submenu).
* The parent item is not navigable — clicking toggles the children.
*/
function NavSubMenu({ item, currentPath }: { item: NavItem; currentPath: string }) {
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.

NavSubMenu (and the children field on NavItem) is wired up to render but nothing in this PR ever populates children — the "Menus → Header / Footer" nested example in the PR description doesn't actually exist in code. No surface allows children to be set either: ManifestCollection has no children field and PluginAdminPage only gained group/sortOrder.

Not a blocker, but worth either wiring up the Menus -> menu-instances case the PR description promises, or trimming the dead code/docs so reviewers and future contributors don't have to figure out how to populate something that has no entry point.

.where("name", "=", menuName)
.executeTakeFirst();

if (!menu) return; // Menu doesn't exist
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.

syncCollectionToMenu silently returns when the named menu doesn't exist. Combined with addToMenu being a free-form string on CreateCollectionInput, a user who typos addToMenu: "primry" will get a successfully-created collection and no menu item, no warning, no error. At minimum log the miss, and ideally validate the menu name up front in handleSchemaCollectionCreate so the API call fails fast with a useful error.

collections: z.array(
z.object({
slug: z.string().min(1),
sortOrder: z.number().int(),
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.

The Zod schema doesn't bound sortOrder (negatives are accepted) or limit the collections array length. A client can POST 100k entries and force 100k+ DB round-trips before the handler errors out. Consider .min(0) on sortOrder and a reasonable .max(N) on the array (the number of collections a site has is small).

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 14, 2026

Review posted successfully (review ID 4289278513). The review covers:

  1. Bug — Media link duplicated in every named collection group (Sidebar.tsx:387)
  2. BugapplyMenuSyncDiff derives target menu from diff.toAdd[0], broken when only removing/reordering (menu-sync.ts:119)
  3. i18n correctness — Menu lookups ignore (name, locale) unique key; cross-locale matching on multi-locale installs (menu-sync.ts:52)
  4. Robustness — apply not wrapped in a transaction (menu-sync.ts:167)
  5. Style/AGENTS — Bare catch {} swallowing errors throughout (menu-sync.ts:92)
  6. InconsistencylistCollectionsWithFields not updated to order by sort_order (registry.ts:116 + summary)
  7. PerformancereorderCollections is 2N round-trips, no transaction (registry.ts:707)
  8. Dead featureNavSubMenu / NavItem.children wired up but never populated; doesn't match PR description (Sidebar.tsx:233)
  9. UXaddToMenu silently no-ops on typos (schema.ts:615)
  10. Input validationreorder endpoint accepts unbounded payload and negative sort orders (reorder.ts:21)
  11. Doc nit — PR body says "Migration 038", actual file is 039
  12. Test buglistCollectionsWithFields ordering test passes for the wrong reason

github run

ahliweb and others added 3 commits May 14, 2026 18:34
- Fix Media duplication in every named collection group (Sidebar.tsx)
- Fix applyMenuSyncDiff deriving menu from toAdd[0]; pass menuName explicitly
- Add locale filtering to menu sync queries (menu-sync.ts, schema.ts)
- Wrap applyMenuSyncDiff in withTransaction for atomicity
- Replace bare catch {} with proper error logging
- Optimize reorderCollections: batch validation + transaction
- Fix reorder endpoint Zod validation: bound sortOrder >= 0, array max 200
- Fix addToMenu warning on missing menu; validate menu in handleSchemaCollectionMenuSync
- Fix listCollectionsWithFields ordering test with disagreeing inputs
- Add locale field to test menu creation
@ahliweb
Copy link
Copy Markdown
Contributor Author

ahliweb commented May 14, 2026

@ascorbic @ask-bonk /review

@ascorbic
Copy link
Copy Markdown
Collaborator

/review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Most of the issues from the prior review have been addressed — nicely done. The big-ticket items (Media duplication, applyMenuSyncDiff deriving its target from diff.toAdd[0], transaction wrapping, locale filtering in menu-sync.ts, listCollectionsWithFields ordering, transactional/batched reorderCollections, the test fixture fix, and the reorder endpoint's input bounds) are all resolved.

A few residual concerns worth a follow-up before merge:

  1. NavSubMenu / NavItem.children is still dead code — nothing in the PR populates children. The PR description still promises a Menus → Header / Footer nested case that has no entry point.
  2. addToMenu on collection creation still silently no-ops on typos. syncCollectionToMenu logs console.warn but the API caller gets a successful response.
  3. The sync route handlers (sync.ts, sync-menu.ts) hardcode locale = "en" — on multi-locale installs you can only sync the English menu through the API.
  4. The admin client's fetchMenuSyncDiff / applyMenuSync are exported but unused — no UI surface calls them in this PR. Worth either wiring them up or holding them back to a follow-up to keep this PR focused.
  5. PR description nit: still says "Migration 038" but the file is 039_collection_grouping.ts.

None are merge blockers in my view; the locale and silent-no-op ones are the most worth tightening before this lands.

/** Optional badge count (e.g., pending comments) */
badge?: number;
/** Child items for nested submenu (max 1 level) */
children?: NavItem[];
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.

NavItem.children is declared and the NavSubMenu renderer (lines 233-302) is wired up to handle it, but nothing in this PR ever populates children — collection groups and plugin groups are flattened into NavGroup.items (lines 381-389, 484-487), and ManifestCollection / PluginAdminPage have no children field. The "Menus → Header / Footer" nested example in the PR description has no path to existing.

Either wire up the Menus → menu-instances case the PR description shows, or drop NavSubMenu + the children field for now so future contributors aren't trying to populate something with no entry point.


// Sync to public menu if requested
if (input.addToMenu) {
await syncCollectionToMenu(db, item.slug, item.label, input.addToMenu);
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.

syncCollectionToMenu now logs a warning when the named menu doesn't exist (good), but handleSchemaCollectionCreate still doesn't surface that miss to the API caller — a user who typos addToMenu: "primry" gets a 200 with a created collection and no menu item, no error, no warning visible from the client side.

Either validate the menu up front (mirror what handleSchemaCollectionMenuSync already does at lines 698-711) and 400 on miss, or have syncCollectionToMenu return a result that the create handler can attach to the response so the client knows. Logging server-side isn't enough for a feature whose whole point is auto-wiring sidebar items into the user's menu.

const denied = requirePerm(user, "menus:manage");
if (denied) return denied;

const result = await computeMenuSyncDiff(emdash!.db, name);
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.

locale is hardcoded to the function's default ("en") in both GET (here) and POST (line 54). On a multi-locale install — which is the whole reason migration 036_i18n_menus_and_taxonomies made _emdash_menus keyed by (name, locale) — there's no way to sync the Arabic or French version of primary through this API.

Accept a locale query parameter and forward it. Same applies to sync-menu.ts (line 41) and schema.ts's handleSchemaCollectionCreate calling syncCollectionToMenu without a locale.

If this is intentionally en-only for now, document it explicitly in the route comment and have it 400 on multi-locale sites instead of silently picking the en menu.

Comment thread packages/admin/src/lib/api/menus.ts Outdated
/**
* Apply sync changes (align menu with sidebar structure).
*/
export async function applyMenuSync(name: string): Promise<SyncResult> {
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.

fetchMenuSyncDiff and applyMenuSync are exported from lib/api/menus.ts and re-exported from lib/api/index.ts but no UI in this PR consumes them — the only callers are the exports themselves. Either wire up the diff-preview/apply UI promised in the PR description, or drop these helpers and fetchMenuSyncDiff / applyMenuSync server routes until the UI is ready. Shipping an API surface with no consumer makes it harder to evolve the contract later, because anyone could write to it.

Also, neither helper accepts a LocaleOptions parameter the way every other menu function in this file does — same multi-locale gap as the server route.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 14, 2026

Review posted successfully (review ID 4289894663) with 4 inline comments and a summary. The author has addressed most of the critical issues from the prior review (Media dup, transaction wrapping, locale filtering, listCollectionsWithFields ordering, reorder batching/validation, input bounds, test fixture). Remaining concerns are smaller follow-ups:

  1. Dead codeNavSubMenu / NavItem.children declared but never populated; PR description claims a feature that doesn't exist
  2. Silent no-opaddToMenu on collection create still 200s on typo'd menu names; only logs a warning
  3. Hardcoded "en" locale — sync route handlers can't sync non-English menus on multi-locale sites
  4. Unused API surfacefetchMenuSyncDiff/applyMenuSync exported but no UI calls them; also missing LocaleOptions
  5. Doc nit — PR body still says "Migration 038" but the file is 039

github run

ahliweb and others added 2 commits May 14, 2026 19:42
- Accept locale from query params in menu sync endpoints (sync.ts)
- Validate menu exists before collection creation when addToMenu is specified (schema.ts)
- Change syncCollectionToMenu to return boolean for success/failure
- Remove dead exports fetchMenuSyncDiff and applyMenuSync from menus.ts and index.ts
@ahliweb
Copy link
Copy Markdown
Contributor Author

ahliweb commented May 14, 2026

@ascorbic @ask-bonk /review — Second round of fixes applied: locale from query params, addToMenu validation, dead exports removed. All 32 CI checks green.

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.

2 participants