Navigation — Improvement & Growth Plan
Package: capell-app/navigation · Kind: package · Tier: free · Product group: Capell Foundation · Bundle: foundation · Status: Draft
1. Snapshot
Section titled “1. Snapshot”Navigation owns editable, site- and language-scoped menu trees stored as a JSON items column on the navigations table (one model, src/Models/Navigation.php), plus the page-level “which menus reference this page” panel, sync/replication actions, and the foundation header render hook. Surfaces are admin, frontend, console. Key Actions: BuildNavigationRenderModelAction (view model assembly + per-request memoization), BuildPageNavigationReferencesAction (reverse lookup), AddPageToNavigationAction / RemovePageFromNavigationAction (item mutation with row locking), ReplicateSiteNavigationsAction (site clone). Frontend component rendering is now thin: View/Components/Header/MainNavigation and View/Components/Menu pass context into Blade, and View/Composers/NavigationRenderModelComposer resolves the navigation and hydrated NavigationRenderData for the view. Composer and manifest dependencies now both declare capell-app/admin, capell-app/core, and capell-app/frontend. Current marketplace summary (verbatim): “Build and manage multilingual, per-site menus visually — link to any page or URL, nest dropdowns, and render them in your theme with one tag. Active-state, publish windows, and site cloning included.” Manifest now promotes the marketplace card plus the styled create/edit and site relation-manager captures; empty index, unrelated page-tab, and frontend dashboard captures remain runner evidence until recaptured.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”- Done/Shipped: Replaced the JSON
LIKEpage-references lookup.BuildPageNavigationReferencesActionnow loads candidate navigations through an indexedwhereExistsagainstnavigation_page_references, then keeps the decoded item-tree guard so stale indexed references cannot show false positives in the page form. Coverage asserts noitems LIKE, no standalone pivot scan, the exists lookup is used, and stale rows are ignored. —src/Actions/BuildPageNavigationReferencesAction.php,tests/Integration/Actions/BuildPageNavigationReferencesActionTest.php— M - Done/Shipped: moved frontend navigation resolution out of component
render().Menu::render()andHeader\MainNavigation::render()now return views with context only.NavigationRenderModelComposerresolvesFrontend::site()/page()/language(), loaded site-domain relations,NavigationLoader, andBuildNavigationRenderModelAction, then injectsNavigationRenderDatainto the public views. The Blade views render nothing unless the composer provides a non-empty render model. —src/View/Components/Menu.php,src/View/Components/Header/MainNavigation.php,src/View/Composers/NavigationRenderModelComposer.php,src/Providers/NavigationServiceProvider.php— M - Done/Shipped: Make the declared health check actually check something —
NavigationHealthChecknow probes storage tables, the model morph alias, required main navigations, orphaned pageable references, and Navigation’s own Foundation header render-hook scenarios. Coverage proves unrelated or partial header hooks fail instead of producing a false-green critical check. —src/Health/NavigationHealthCheck.php,src/Support/RenderHooks/RegisterFoundationHeaderNavigationHook.php,tests/Feature/Health/NavigationHealthCheckTest.php— M - Shipped: populate the manifest fields downstream tooling reads —
capabilities[]now declares the package contract (navigation-menu-builder,navigation-page-field,navigation-render-model,navigation-site-replication) andcacheSafety.invalidationSources[]now records the Navigation, Page, and Site invalidation triggers. Evidence:capell.json,tests/Unit/PackageMetadataTest.php. — S - Shipped: add
capell-app/coreto composerrequire— the manifest and composer metadata both declare the direct core dependency already used by the package’sCapell\Core\…imports. This package does not ship acomposer.local.json, so no package-local overlay needed alignment. Evidence:composer.json,capell.json,tests/Unit/PackageMetadataTest.php. — S - Reopened: Reconcile marketplace screenshots with what ships —
capell.jsonnow promotes only buyer-worthy Navigation captures: the marketplace card, create/edit form, and site relation-manager in light/dark modes.docs/screenshots.jsonstill keeps the 5 runner capture surfaces and maps each one to its committeddarkScreenshotPath, but index/page-tab/frontend screenshots need populated recapture before marketplace promotion. Evidence:capell.json,docs/screenshots.json,tests/Unit/PackageMetadataTest.php. — S - Done/Shipped: dropped the per-page
SiteDomainfallback query in the item loader.NavigationItemsLoader::getPagesByMorphKey()now preloads the loaded pages’(site_id, language_id)domain scopes once into a keyed map, then attaches the matchingSiteDomainrelation from memory when a page URL differs from the active domain. —src/Support/Loader/NavigationItemsLoader.php— S - Done/Shipped: reverse-references are memoized for the admin panel.
BuildPageNavigationReferencesActionuses aREQUEST_CACHE_KEY, stores lookups in request attributes, exposesflushRequestCache(), and has coverage proving repeated page form renders reuse the cached references. —src/Actions/BuildPageNavigationReferencesAction.php,tests/Integration/Actions/BuildPageNavigationReferencesActionTest.php— S
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”With manifest capabilities[] now populated, the items below remain real feature gaps relative to navigation norms.
- Done/Shipped: role/permission/auth-conditional item visibility. Navigation items now keep the existing
is_visibleboolean and can additionally setdata.visibilityto everyone, guests, authenticated users, Gate ability, or host-provided role. Ability checks useGate::allows(), role checks callhasRole()only when the authenticated user model provides it, and missing ability/role configuration hides the item. —src/Enums/NavigationItemVisibility.php,src/Support/Loader/NavigationItemsLoader.php,src/Filament/Configurators/Navigations/DefaultNavigationConfigurator.php - Done/Shipped: breadcrumb builder and component.
BuildNavigationBreadcrumbsActionextracts the active branch fromNavigationRenderData, and<x-capell-navigation::breadcrumbs>renders a public breadcrumb trail using the same composer-backed navigation context as<x-capell-navigation::menu>. —src/Actions/BuildNavigationBreadcrumbsAction.php,src/View/Components/Breadcrumbs.php,resources/views/components/breadcrumbs.blade.php,src/Providers/NavigationServiceProvider.php - Done/Shipped: richer external links and targets.
NavigationItemTypenow has a first-classExternalLinkcase,NavigationItemTargetsupports_self,_blank, and_parent, the admin item form exposes an optionalrelattribute, render models carryrel, and public menu Blade emits saferel="noopener noreferrer"defaults for new-tab external links when no custom rel is set. Remaining richer-item depth: dividers and arbitrary content-model links. —src/Enums/NavigationItemType.php,src/Enums/NavigationItemTarget.php,src/Filament/Configurators/Navigations/DefaultNavigationConfigurator.php,src/Actions/BuildNavigationRenderModelAction.php,resources/views/components/menu-items.blade.php,resources/views/components/header/menu/item.blade.php - Done/Shipped: Mega-menu / column layout support. Parent items can opt into
dropdown_layout=mega, choose one to four child-link columns, and show an optional panel heading/description/link in the header dropdown renderer. The render model passes only public layout keys through the existing view-data allow-list. —src/Enums/NavigationDropdownLayout.php,src/Filament/Configurators/Navigations/DefaultNavigationConfigurator.php,src/Actions/BuildNavigationRenderModelAction.php,resources/views/components/header/menu/dropdown.blade.php - Done/Shipped: per-menu / per-handle programmatic registration.
NavigationHandleRegistrynow preserves the built-in enum defaults while allowing themes and packages to register extra handles such asaccount-menuormega-header. The admin form reads registry options, while stored navigation keys remain strings so existing enum callers and custom handles can coexist. —src/Support/Registry/NavigationHandleRegistry.php,src/Filament/Configurators/Navigations/DefaultNavigationConfigurator.php - Done/Shipped: opt-in starts-with active-state mode. Navigation items now default to exact active matching but can opt into
starts_withviadata.active_modefrom the admin item form. Link/external-link items use the mode directly, and page items keep exact record matching first before falling back to their resolved URL for section highlighting. —src/Enums/NavigationItemActiveMode.php,src/Support/Loader/NavigationItemsLoader.php,src/Filament/Configurators/Navigations/DefaultNavigationConfigurator.php - Done/Shipped: public render-model cache (cross-request).
BuildNavigationRenderModelActionnow keeps the existing per-request cache and adds a short cross-request cache for anonymous render contexts only, keyed by navigation/site/language/page/domain and relevantupdated_atstamps. Authenticated requests remain request-local so role/ability visibility decisions cannot leak between users. Navigation saves and page URL changes flush the tag-backed shared render cache where supported. —src/Actions/BuildNavigationRenderModelAction.php,src/Observers/NavigationObserver.php,src/Providers/NavigationServiceProvider.php
4. Issues / Risks
Section titled “4. Issues / Risks”- Done/Shipped: admin page reverse lookup avoids JSON
LIKE. The page form now uses thenavigation_page_referenceslookup table through a correlatedexistsquery and still validates decoded navigation items to guard stale index rows. —src/Actions/BuildPageNavigationReferencesAction.php,tests/Integration/Actions/BuildPageNavigationReferencesActionTest.php - Done/Shipped: critical health check is real. Navigation Diagnostics now fail on missing tables, missing morph alias, missing main navigation coverage, orphaned pageable references, and missing/partial package-owned header render-hook registration. —
src/Health/NavigationHealthCheck.php,tests/Feature/Health/NavigationHealthCheckTest.php - Done/Shipped: public components no longer lazy-load in
render(). The view composer owns navigation/render-model resolution and the components return views with context only, keeping the component render methods free of loader/action calls. —src/View/Components/Menu.php,src/View/Components/Header/MainNavigation.php,src/View/Composers/NavigationRenderModelComposer.php NavigationLoader::getNavigation()falls back towhereNull('language_id')site-wide query then in-PHP sort — whensiteOnlyFallbackis true and no language match is found, it loads all published navigations for the site into memory and sorts in PHP. Fine for a handful of menus, but unbounded by design. —src/Support/Loader/NavigationLoader.php- Observer cache invalidation can fan out to every site —
NavigationObserver::clearCache()mergesSite::query()->pluck('id')(all sites) into the keys-to-clear set wheneversite_idis null on either the current or original record. A single global-navigation save invalidates per-site keys for the entire install. Correct, but a thundering-herd risk on large multi-site installs; the manifest setsqueueInvalidation: truebutinvalidationSources: []is undocumented. —src/Observers/NavigationObserver.php - No cross-request cache safety test for anonymous output — Capell requires tests proving anonymous/non-admin render safety for cache/render changes. There are frontend render tests (
tests/Feature/Frontend/Page/PageNavigationTest.php,PagePerformanceNavigationTest.php) and the boundary arch test (tests/Arch/NavigationBoundaryTest.php) only asserts it doesn’t import downstream packages and uses strict equality. There is no test asserting the rendered menu HTML leaks no admin internals (model IDs, field paths,pageable_type, signed editor URLs) for an anonymous visitor. The admin page panel does emitNavigationResource::getUrl('edit')links — confirm that Blade (page/navigations.blade.php) is never reachable on a public surface. —tests/Arch/NavigationBoundaryTest.php,resources/views/components/page/navigations.blade.php NavigationItemDatais mutated in place during loading —NavigationItemsLoader::load()reassigns$this->navigation->itemsand flips$item->active = trueon shared data objects;BuildNavigationRenderModelActionmemoizes by a cache key that includesspl_object_idfor unsaved navigations. Mutating model state during a read path is a subtle cache-coherence footgun if the sameNavigationinstance is rendered in two contexts. —src/Support/Loader/NavigationItemsLoader.php- i18n: item-type and target labels are translated (
getLabel()via__()), and the lang file isresources/lang/en/generic.php. Coverage looks thin (onegeneric.php); verify the header Blade’s hardcodedaria-label/sr-onlystrings all route through__()(several do, e.g.capell-navigation::generic.main_navigation). —resources/lang/en/generic.php - Test gaps: well-covered — all 6 Actions, both loaders, observer, adapters, content-graph extractor, both commands, policy, registry, and Filament resources/relation-manager all have Pest files. Notably untested: cross-request public-output safety (above), the
LIKE-fragment edge cases inBuildPageNavigationReferencesAction(e.g. apageable_idsubstring collision like 12 matching 123 — the%"pageable_id":12%fragment can false-positive before the in-PHPnavigationContainsRecordfilter rescues it), and active-state for external links. —tests/
5. Marketplace & Positioning
Section titled “5. Marketplace & Positioning”This is a free, foundation-bundle, first-party package — correctly positioned: navigation is non-negotiable infrastructure every Capell site needs, so it belongs in the free foundation tier as a platform credibility piece, not a revenue line.
- Shipped marketplace
summary: “Build and manage multilingual, per-site menus visually — link to any page or URL, nest dropdowns, and render them in your theme with one tag. Active-state, publish windows, and site cloning included.” Evidence:capell.json,tests/Unit/PackageMetadataTest.php. - Shipped composer
description: “Site- and language-scoped navigation menus for Capell: visual menu builder, page & link items, nested dropdowns, active-state rendering, publish scheduling, and multi-site replication.” Evidence:composer.json,tests/Unit/PackageMetadataTest.php. - free/bundle vs premium: keep free/foundation. The premium upsell is not this package — it is a future
navigation-pro(mega-menus, role-conditional visibility, breadcrumbs-as-a-service, A/B menu variants) that depends on this one’s contract. That dependency only works ifcapabilities[]is populated (Section 2/4). - Screenshot/media coverage: manifest now advertises the marketplace card plus the create/edit and site relation-manager captures. The empty index, unrelated page-tab, and frontend dashboard captures were demoted and must be recaptured with real menu data before completion.
- Platform-pitch contribution: navigation is the proof that Capell’s “editors manage structure without touching theme code” promise is real. The README’s “Why It Helps Your Capell Workflow” framing is the right pitch; surface it in the marketplace summary, not just the README.
- Keywords/tags (8–12):
navigation,menus,menu-builder,multilingual,multi-site,dropdown,mega-menu,breadcrumbs,active-state,cms,filament,foundation.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
Done/Shipped: Implement real NavigationHealthCheck probes. Evidence: storage, morph alias, main navigation, orphaned reference, and package-owned header hook diagnostics are covered. | Done | M | High | 2, 4 |
Done/Shipped: Replace JSON LIKE '%...%' reverse-lookup with pivot/indexed lookup. Evidence: reverse lookup now uses whereExists against navigation_page_references, avoids standalone pivot scans, and ignores stale indexed references. | Done | M | High | 2, 4 |
Add capell-app/core to composer require | Done | S | Med | 2 |
Populate manifest capabilities[] + cacheSafety.invalidationSources[] | Done | S | High | 2, 4 |
| Recapture populated index/page-reference/frontend-menu screenshots before promoting the remaining Navigation product media. Evidence: weak captures were demoted while styled create/edit and site relation-manager captures remain promoted. Deferred until styled runner recapture; no implementation blocker. | Later | S | Med | 1, 5 |
Rewrite marketplace summary + composer description | Done | S | Med | 5 |
| Add anonymous/non-admin public-output safety test for rendered menu HTML | Done | S | High | 4 |
Done/Shipped: Move DB resolution out of frontend component render() into a resolver/composer | Done | M | High | 2, 4 |
Done/Shipped: Add role/permission/auth-conditional item visibility. Evidence: item visibility supports everyone, guests, authenticated users, Gate ability, and host-provided hasRole() role checks from the loader and admin form. | Done | L | High | 3 |
Done/Shipped: Add breadcrumb builder action + component. Evidence: BuildNavigationBreadcrumbsAction extracts the active render branch, and <x-capell-navigation::breadcrumbs> renders it through the existing composer-backed context. | Done | M | High | 3 |
Done/Shipped: Add ancestor/starts-with active-state mode for section highlighting. Evidence: items can opt into starts_with active matching while exact remains the default; page items preserve record matching before URL-prefix fallback. | Done | M | Med | 3 |
Done/Shipped: First-class external-link item type + rel/_self/_parent targets. Evidence: ExternalLink item type, expanded target enum, admin rel field, render-model rel data, and public Blade rel output are wired. | Done | S | Med | 3 |
Done/Shipped: Programmatic menu-handle registry (replace fixed NavigationHandle enum) | Done | M | Med | 3 |
| Done/Shipped: Cross-request site/locale-scoped cached render model (hit 20ms budget) | Done | M | Med | 3, 4 |
Done/Shipped: Mega-menu / multi-column dropdown primitive. Evidence: parent items now expose dropdown_layout=mega, column count, and optional intro panel fields, and the header dropdown renderer switches to a valid HTML div/panel plus grid list for mega menus. | Done | L | Med | 3 |
Done/Shipped: Preload site domains once in item loader (drop per-page fallback query). Evidence: fallback pageUrl domains are loaded into a keyed (site_id, language_id) map before the page loop. | Done | S | Low | 2 |