diff --git a/docs/AUDIT-FOLLOWUPS.md b/docs/AUDIT-FOLLOWUPS.md index c6253253..9a3b3581 100644 --- a/docs/AUDIT-FOLLOWUPS.md +++ b/docs/AUDIT-FOLLOWUPS.md @@ -15,20 +15,20 @@ message order where possible. ## Quick status snapshot โ€” 2026-05-08 23:00 -| Wave | Topic | Status | -| --- | --- | --- | -| 1 | Small confident fixes | โœ… Done | -| 2 | Country dropdown unification + cmdk scroll | โœ… Done (country/nationality split deferred) | -| 3 | Berth field overhaul (NocoDB enums) | โœ… Done | -| 4 | Currency platform-wide | ๐Ÿ”ด Not started | -| 5 | Configurable enums (admin Vocabularies) | ๐Ÿ”ด Not started | -| 6 | Notes unification (aggregate-on-read) | ๐Ÿ”ด Not started | -| 7 | Clients / yachts / companies misc | ๐ŸŸก Partial (small bits done; large items deferred) | -| 8 | Expenses revisit | ๐Ÿ”ด Not started | -| 9 | Interests + notifications | โœ… Done | -| 10 | Settings polish | โœ… Done (small bits) โ€” schema-blocked items deferred | -| 11 | DEFERRED โ€” group-discussion items | ๐ŸŸฃ Awaiting alignment | -| **Bonus** | **Public berth feed (website map)** | โœ… Map data backfilled (117 rows). Field-parity gaps remain โ€” see ยง Bonus. | +| Wave | Topic | Status | +| --------- | ------------------------------------------ | -------------------------------------------------------------------------- | +| 1 | Small confident fixes | โœ… Done | +| 2 | Country dropdown unification + cmdk scroll | โœ… Done (country/nationality split deferred) | +| 3 | Berth field overhaul (NocoDB enums) | โœ… Done | +| 4 | Currency platform-wide | ๐Ÿ”ด Not started | +| 5 | Configurable enums (admin Vocabularies) | ๐Ÿ”ด Not started | +| 6 | Notes unification (aggregate-on-read) | ๐Ÿ”ด Not started | +| 7 | Clients / yachts / companies misc | ๐ŸŸก Partial (small bits done; large items deferred) | +| 8 | Expenses revisit | ๐Ÿ”ด Not started | +| 9 | Interests + notifications | โœ… Done | +| 10 | Settings polish | โœ… Done (small bits) โ€” schema-blocked items deferred | +| 11 | DEFERRED โ€” group-discussion items | ๐ŸŸฃ Awaiting alignment | +| **Bonus** | **Public berth feed (website map)** | โœ… Map data backfilled (117 rows). Field-parity gaps remain โ€” see ยง Bonus. | Test status: `pnpm exec vitest run` โ†’ **1185/1185 pass**. TS check: `pnpm exec tsc --noEmit` โ†’ **clean**. @@ -83,7 +83,7 @@ Git: 23 files modified, 2 new files (no commits yet). below (pipeline funnel, occupancy timeline, revenue breakdown, lead source) and the activity feed remain. File: `src/components/dashboard/dashboard-shell.tsx`. -3. **Per-dock color stripe on mobile berth cards** โ€” was the *status* +3. **Per-dock color stripe on mobile berth cards** โ€” was the _status_ color, which made every same-dock berth different. Now uses `mooringLetterDot()` so the stripe groups by dock letter; status conveyed by the existing pill below. @@ -114,20 +114,20 @@ Git: 23 files modified, 2 new files (no commits yet). Files: `src/components/layout/sidebar.tsx`, `src/components/layout/mobile/more-sheet.tsx`. 10. **"Other" comm-channel UX hint** โ€” when a contact's channel is - `'other'`, the inline `Label` field switches its label/placeholder - to "Specify" / "e.g. Telegram, Signal". - File: `src/components/clients/client-form.tsx:289-302`. + `'other'`, the inline `Label` field switches its label/placeholder + to "Specify" / "e.g. Telegram, Signal". + File: `src/components/clients/client-form.tsx:289-302`. 11. **End Membership wording** โ€” renamed to "Remove from company" in - the company members tab dropdown. - File: `src/components/companies/company-members-tab.tsx:249`. + the company members tab dropdown. + File: `src/components/companies/company-members-tab.tsx:249`. 12. **Berth area filter โ†’ letter dropdown** โ€” was free-text; now a - `` constrained to `A / B / C / D / E`. Label changed to + "Dock" to match how the user refers to it. + File: `src/components/berths/berth-filters.tsx`. 13. **Yacht flag โ†’ CountryCombobox** โ€” was a free-text 2-letter input - (`placeholder="e.g. MT"`); now uses the same country picker as - client / residential. - File: `src/components/yachts/yacht-form.tsx`. + (`placeholder="e.g. MT"`); now uses the same country picker as + client / residential. + File: `src/components/yachts/yacht-form.tsx`. ### Wave 2 โ€” country dropdown unification @@ -149,7 +149,7 @@ Git: 23 files modified, 2 new files (no commits yet). floors so wide triggers get wide popovers. 6. **DEFERRED: country/nationality split** on the client form โ€” needs a Drizzle migration (`alter table clients add column country_iso - text`) plus a copy-on-migrate of existing `nationality_iso` values. +text`) plus a copy-on-migrate of existing `nationality_iso` values. See ยง Wave 11 / pending โ€” large. ### Wave 3 โ€” berth field overhaul (NocoDB enums) @@ -210,6 +210,7 @@ Triggered by user prompt "ensure we are properly wired up to replace the NocoDB table as the source of truth for the berth map". **State before audit:** + - API endpoints existed (`/api/public/berths`, `/api/public/berths/[mooringNumber]`) โ€” wiring fine. - `src/lib/services/public-berths.ts` mapped the response shape to @@ -220,8 +221,9 @@ the NocoDB table as the source of truth for the berth map". has no shapes to render. **Action taken:** + - Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply - --port-slug port-nimara` (after a clean dry-run). Result: +--port-slug port-nimara` (after a clean dry-run). Result: 117 berths updated, 117 `berth_map_data` rows inserted. - Spot-checked the public API: `GET /api/public/berths` returns the correct shape with `Map Data` populated, byte-for-byte identical @@ -288,14 +290,16 @@ User chose option 1 ("aggregate on read") from the brainstorm. The yacht notes into a single feed with `source` metadata. Symmetric extensions to add: + - `listForYachtAggregated` โ€” yacht own notes + owner client notes - + linked interest notes. + - linked interest notes. - `listForCompanyAggregated` โ€” company own notes + owned yacht notes - + linked interest notes. + - linked interest notes. - `listForResidentialClientAggregated` โ€” residential client notes - + residential interest notes. + - residential interest notes. UI: + - `` should render the source-label badge (already implemented for clients โ€” copy the pattern). - Convert single-textarea spots to entry-list pattern: the @@ -310,11 +314,13 @@ UI: ### Wave 7: clients / yachts / companies misc Done in this session: + - **Yacht flag** โ†’ CountryCombobox (Wave 1). - **End Membership** โ†’ "Remove from company" (Wave 1). - **Berth Documents tab** explainer paragraph. Pending: + - **Status change modal โ€” prospect picker**: when user changes berth status to `under_offer` or `sold`, surface an interest/prospect selector below the reason dropdown so the recorded reason can link @@ -338,8 +344,8 @@ Pending: Chromium. User reported "doesn't open" on macOS โ€” possibly a focus / window issue or a content-blocking extension. Need a real-machine repro to diagnose. The hidden `` - + `fileInputRef.current?.click()` wiring is at - `user-settings.tsx:247-258`. + - `fileInputRef.current?.click()` wiring is at + `user-settings.tsx:247-258`. - **Display name + first / last name fields** โ€” current schema only has `displayName`. Adding first/last requires a Drizzle migration on `users` or `user_profiles` plus migration of existing data (split @@ -351,24 +357,24 @@ Pending: ### Wave Bonus follow-up โ€” public berth feed field parity -Map data is now wired. Field gaps the website *might* consume but we +Map data is now wired. Field gaps the website _might_ consume but we don't expose: -| NocoDB field | Currently in PublicBerth? | DB has it? | Notes | -| --- | --- | --- | --- | -| `Price` | โŒ | โœ… `berths.price` | Pricing-public is a policy decision. **Open question (#4)** | -| `Berth Approved` | โŒ | โœ… `berths.berth_approved` | Boolean. Often used to gate "Sold" display | -| `Water Depth` | โŒ | โœ… `berths.water_depth` | Sometimes shown in tooltip | -| `Width Is Minimum` | โŒ | โœ… `berths.width_is_minimum` | Modifier for "Width" display | -| `Water Depth Is Minimum` | โŒ | โœ… `berths.water_depth_is_minimum` | ditto | -| `Length (Metric)` | โŒ | โœ… `berths.length_m` | Derivable. Website may consume | -| `Width (Metric)` | โŒ | โœ… `berths.width_m` | ditto | -| `Draft (Metric)` | โŒ | โœ… `berths.draft_m` | ditto | -| `Water Depth (Metric)` | โŒ | โœ… `berths.water_depth_m` | ditto | -| `Nominal Boat Size (Metric)` | โŒ | โœ… `berths.nominal_boat_size_m` | ditto | -| `CreatedAt` / `UpdatedAt` | โŒ | โœ… timestamps | Cache invalidation hints | -| `Interests` (count) | โŒ | derivable | Probably internal-only | -| `Interested Parties` (count) | โŒ | derivable | Probably internal-only | +| NocoDB field | Currently in PublicBerth? | DB has it? | Notes | +| ---------------------------- | ------------------------- | ---------------------------------- | ----------------------------------------------------------- | +| `Price` | โŒ | โœ… `berths.price` | Pricing-public is a policy decision. **Open question (#4)** | +| `Berth Approved` | โŒ | โœ… `berths.berth_approved` | Boolean. Often used to gate "Sold" display | +| `Water Depth` | โŒ | โœ… `berths.water_depth` | Sometimes shown in tooltip | +| `Width Is Minimum` | โŒ | โœ… `berths.width_is_minimum` | Modifier for "Width" display | +| `Water Depth Is Minimum` | โŒ | โœ… `berths.water_depth_is_minimum` | ditto | +| `Length (Metric)` | โŒ | โœ… `berths.length_m` | Derivable. Website may consume | +| `Width (Metric)` | โŒ | โœ… `berths.width_m` | ditto | +| `Draft (Metric)` | โŒ | โœ… `berths.draft_m` | ditto | +| `Water Depth (Metric)` | โŒ | โœ… `berths.water_depth_m` | ditto | +| `Nominal Boat Size (Metric)` | โŒ | โœ… `berths.nominal_boat_size_m` | ditto | +| `CreatedAt` / `UpdatedAt` | โŒ | โœ… timestamps | Cache invalidation hints | +| `Interests` (count) | โŒ | derivable | Probably internal-only | +| `Interested Parties` (count) | โŒ | derivable | Probably internal-only | **Plan once questions are answered:** Add the chosen fields to `PublicBerth` interface in `src/lib/services/public-berths.ts`, the @@ -376,6 +382,7 @@ don't expose: by which fields the website actually uses. **Other public-feed concerns to flag**: + - **No archive flag**: when a berth is retired the public feed will still serve it. Need a `berths.archived_at` column + filter on the route. Plan ยง4.5 hinted at this. Not urgent. @@ -404,6 +411,7 @@ berths inline (without leaving the form), plus a mini-recommender for picking a berth at create time. Scope: + - "Existing yacht / new yacht" picker. - "Existing company / new company" picker. - "Open an interest with this client" affordance that wires through @@ -417,6 +425,7 @@ Estimate fully before starting (likely 2โ€“3 days). ### B. Documents section overhaul User wants: + - Folders (create / delete / nested). - Sort + filter (by date, type, owner). - Wider file-type allowlist (PDF + Office + image is current; expand). @@ -433,6 +442,7 @@ Refactor of `documents.service.ts` plus a new folders schema ### C. Reports system User asked for: + - Defined report types (Pipeline summary / Revenue / Activity log / Berth occupancy) with documented data shape per type. - Test fixtures for visual QA. @@ -453,11 +463,13 @@ PDF pages via `pdf-lib.copyPages`. Depends on Wave 8 expense form work. ### E. Country / Nationality split on Client form Client schema has only `nationalityIso`. User wants: -- New `country_iso` column for *country of residence* (visible + +- New `country_iso` column for _country of residence_ (visible / primary). -- Keep `nationality_iso` as an *optional* secondary field. +- Keep `nationality_iso` as an _optional_ secondary field. Requires: + - Drizzle migration (`alter table clients add column country_iso text`). - Migrate existing data: copy `nationality_iso โ†’ country_iso` for every client (current value is more often country of residence in @@ -494,7 +506,7 @@ implementation. They're ordered by what unblocks the most work. **Recommendation: one new page, grouped by domain.** Easier to discover, cleaner schema for the editor. 2. **Notification preferences placement (Wave 10)** โ€” keep both the - user-settings notifications panel *and* `/notifications/preferences`, + user-settings notifications panel _and_ `/notifications/preferences`, or collapse to one? **Recommendation: collapse to user-settings only**, since that's where every other personal preference lives. Keep the dedicated route as a redirect for back-compat links. @@ -543,7 +555,7 @@ implementation. They're ordered by what unblocks the most work. every berth (NocoDB does). Confirmation that the live import we ran today populated mooring_type for all 117 records. (Spot- checked: yes โ€” the SQL traces show `mooring_type = "Side Pier / - Med Mooring"` etc.) +Med Mooring"` etc.) --- @@ -551,55 +563,55 @@ implementation. They're ordered by what unblocks the most work. ### Berth-related -| Concern | File(s) | -| --- | --- | -| Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) | -| Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` | -| Berth detail inline edit | `src/components/berths/berth-tabs.tsx` | -| Berth modal form | `src/components/berths/berth-form.tsx` | -| Berth area filter | `src/components/berths/berth-filters.tsx` | -| Berth detail header / status modal | `src/components/berths/berth-detail-header.tsx:90` | -| Berth Documents tab | `src/components/berths/berth-documents-tab.tsx` | -| Berth list query + sort | `src/lib/services/berths.service.ts:25-140` | -| Berth import script | `scripts/import-berths-from-nocodb.ts` | -| Berth import service / parsers | `src/lib/services/berth-import.ts` | -| Public berth API route | `src/app/api/public/berths/route.ts` | -| Public berth single route | `src/app/api/public/berths/[mooringNumber]/route.ts` | -| Public berth mapper | `src/lib/services/public-berths.ts` | -| Public berth tests | `tests/unit/services/public-berths.test.ts` | -| Berth seed snapshot | `src/lib/db/seed-data/berths.json` | -| Berth schema | `src/lib/db/schema/berths.ts` (incl. `berthMapData`) | +| Concern | File(s) | +| ---------------------------------- | ---------------------------------------------------- | +| Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) | +| Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` | +| Berth detail inline edit | `src/components/berths/berth-tabs.tsx` | +| Berth modal form | `src/components/berths/berth-form.tsx` | +| Berth area filter | `src/components/berths/berth-filters.tsx` | +| Berth detail header / status modal | `src/components/berths/berth-detail-header.tsx:90` | +| Berth Documents tab | `src/components/berths/berth-documents-tab.tsx` | +| Berth list query + sort | `src/lib/services/berths.service.ts:25-140` | +| Berth import script | `scripts/import-berths-from-nocodb.ts` | +| Berth import service / parsers | `src/lib/services/berth-import.ts` | +| Public berth API route | `src/app/api/public/berths/route.ts` | +| Public berth single route | `src/app/api/public/berths/[mooringNumber]/route.ts` | +| Public berth mapper | `src/lib/services/public-berths.ts` | +| Public berth tests | `tests/unit/services/public-berths.test.ts` | +| Berth seed snapshot | `src/lib/db/seed-data/berths.json` | +| Berth schema | `src/lib/db/schema/berths.ts` (incl. `berthMapData`) | ### Other domains -| Concern | File(s) | -| --- | --- | -| Interest stage colors / legend | `src/components/interests/stage-legend.tsx` + `src/lib/constants.ts:STAGE_DOT` | -| Mobile kanban toggle / fallback | `src/components/interests/interest-list.tsx` | -| Country / timezone autoset | `src/components/clients/client-form.tsx` + `src/components/settings/user-settings.tsx` | -| Phone input | `src/components/shared/phone-input.tsx` | -| Country combobox + scroll patch | `src/components/shared/country-combobox.tsx` + `src/components/ui/command.tsx` | -| Sidebar Umami gate | `src/components/layout/sidebar.tsx` (search `umamiRequired`) | -| Mobile More-sheet | `src/components/layout/mobile/more-sheet.tsx` | -| Notes service (aggregate-on-read) | `src/lib/services/notes.service.ts:130-242` | -| Notes UI | `src/components/shared/notes-list.tsx` | -| Settings manager (admin) | `src/components/admin/settings/settings-manager.tsx` | -| User settings page | `src/components/settings/user-settings.tsx` | -| Status change dialog | `src/components/berths/berth-detail-header.tsx:90` | -| Companies members tab | `src/components/companies/company-members-tab.tsx` | -| Yacht form | `src/components/yachts/yacht-form.tsx` | -| Client form | `src/components/clients/client-form.tsx` | +| Concern | File(s) | +| --------------------------------- | -------------------------------------------------------------------------------------- | +| Interest stage colors / legend | `src/components/interests/stage-legend.tsx` + `src/lib/constants.ts:STAGE_DOT` | +| Mobile kanban toggle / fallback | `src/components/interests/interest-list.tsx` | +| Country / timezone autoset | `src/components/clients/client-form.tsx` + `src/components/settings/user-settings.tsx` | +| Phone input | `src/components/shared/phone-input.tsx` | +| Country combobox + scroll patch | `src/components/shared/country-combobox.tsx` + `src/components/ui/command.tsx` | +| Sidebar Umami gate | `src/components/layout/sidebar.tsx` (search `umamiRequired`) | +| Mobile More-sheet | `src/components/layout/mobile/more-sheet.tsx` | +| Notes service (aggregate-on-read) | `src/lib/services/notes.service.ts:130-242` | +| Notes UI | `src/components/shared/notes-list.tsx` | +| Settings manager (admin) | `src/components/admin/settings/settings-manager.tsx` | +| User settings page | `src/components/settings/user-settings.tsx` | +| Status change dialog | `src/components/berths/berth-detail-header.tsx:90` | +| Companies members tab | `src/components/companies/company-members-tab.tsx` | +| Yacht form | `src/components/yachts/yacht-form.tsx` | +| Client form | `src/components/clients/client-form.tsx` | ### Infrastructure -| Concern | File(s) | -| --- | --- | -| Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` | -| `system_settings` table | `src/lib/db/schema/system.ts:128-147` | -| Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` | -| Body parsing (always use `parseBody`) | `src/lib/api/route-helpers.ts` | -| Storage backend abstraction | `src/lib/storage/` | -| Logger (pino) | `src/lib/logger.ts` | +| Concern | File(s) | +| ------------------------------------------- | --------------------------------------------- | +| Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` | +| `system_settings` table | `src/lib/db/schema/system.ts:128-147` | +| Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` | +| Body parsing (always use `parseBody`) | `src/lib/api/route-helpers.ts` | +| Storage backend abstraction | `src/lib/storage/` | +| Logger (pino) | `src/lib/logger.ts` | --- diff --git a/src/components/berths/berth-documents-tab.tsx b/src/components/berths/berth-documents-tab.tsx index e5c3d490..6ad8ee19 100644 --- a/src/components/berths/berth-documents-tab.tsx +++ b/src/components/berths/berth-documents-tab.tsx @@ -163,10 +163,9 @@ export function BerthDocumentsTab({ berthId }: { berthId: string }) { return (

- Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. - Versioned so a misparse can be rolled back. For documents tied to a - prospect on this berth (EOI, contract, etc.), open the matching deal - in the Interests tab. + Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. Versioned so a + misparse can be rolled back. For documents tied to a prospect on this berth (EOI, contract, + etc.), open the matching deal in the Interests tab.

diff --git a/src/components/berths/berth-tabs.tsx b/src/components/berths/berth-tabs.tsx index 26693efd..c73008aa 100644 --- a/src/components/berths/berth-tabs.tsx +++ b/src/components/berths/berth-tabs.tsx @@ -245,7 +245,11 @@ function OverviewTab({ berth }: { berth: BerthData }) { /> Phone setPhone(next.e164 ?? '')} placeholder="555 0123" /> diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 7529e12f..e6c24ed2 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -1,12 +1,12 @@ -"use client" +'use client'; -import * as React from "react" -import { type DialogProps } from "@radix-ui/react-dialog" -import { Command as CommandPrimitive } from "cmdk" -import { Search } from "lucide-react" +import * as React from 'react'; +import { type DialogProps } from '@radix-ui/react-dialog'; +import { Command as CommandPrimitive } from 'cmdk'; +import { Search } from 'lucide-react'; -import { cn } from "@/lib/utils" -import { Dialog, DialogContent } from "@/components/ui/dialog" +import { cn } from '@/lib/utils'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; const Command = React.forwardRef< React.ElementRef, @@ -15,13 +15,13 @@ const Command = React.forwardRef< -)) -Command.displayName = CommandPrimitive.displayName +)); +Command.displayName = CommandPrimitive.displayName; const CommandDialog = ({ children, ...props }: DialogProps) => { return ( @@ -32,8 +32,8 @@ const CommandDialog = ({ children, ...props }: DialogProps) => { - ) -} + ); +}; const CommandInput = React.forwardRef< React.ElementRef, @@ -44,15 +44,15 @@ const CommandInput = React.forwardRef<
-)) +)); -CommandInput.displayName = CommandPrimitive.Input.displayName +CommandInput.displayName = CommandPrimitive.Input.displayName; const CommandList = React.forwardRef< React.ElementRef, @@ -64,30 +64,26 @@ const CommandList = React.forwardRef< // event ourselves so the list scrolls regardless of focus state. { - onWheel?.(event) - if (event.defaultPrevented) return - event.currentTarget.scrollTop += event.deltaY + onWheel?.(event); + if (event.defaultPrevented) return; + event.currentTarget.scrollTop += event.deltaY; }} {...props} /> -)) +)); -CommandList.displayName = CommandPrimitive.List.displayName +CommandList.displayName = CommandPrimitive.List.displayName; const CommandEmpty = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >((props, ref) => ( - -)) + +)); -CommandEmpty.displayName = CommandPrimitive.Empty.displayName +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; const CommandGroup = React.forwardRef< React.ElementRef, @@ -96,14 +92,14 @@ const CommandGroup = React.forwardRef< -)) +)); -CommandGroup.displayName = CommandPrimitive.Group.displayName +CommandGroup.displayName = CommandPrimitive.Group.displayName; const CommandSeparator = React.forwardRef< React.ElementRef, @@ -111,11 +107,11 @@ const CommandSeparator = React.forwardRef< >(({ className, ...props }, ref) => ( -)) -CommandSeparator.displayName = CommandPrimitive.Separator.displayName +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; const CommandItem = React.forwardRef< React.ElementRef, @@ -124,30 +120,24 @@ const CommandItem = React.forwardRef< -)) +)); -CommandItem.displayName = CommandPrimitive.Item.displayName +CommandItem.displayName = CommandPrimitive.Item.displayName; -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { +const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( - ) -} -CommandShortcut.displayName = "CommandShortcut" + ); +}; +CommandShortcut.displayName = 'CommandShortcut'; export { Command, @@ -159,4 +149,4 @@ export { CommandItem, CommandShortcut, CommandSeparator, -} +};