QuickNav: een AI-module voor sneller navigeren in Magento Admin

Michael Dibbets

16/10/2026

9 min.

Tijdens de hackathon begonnen we met een kickoff waarin werd uitgelegd dat we dashboard widgets voor het Hyvä Commerce admin dashboard zouden bouwen. Daarna werden wensen en ideeën verzameld.

Ik besloot me te richten op een "QuickNav" widget: een snelle manier om direct naar een product, categorie, setting, CMS page, CMS block, URL rewrite of andere admin-entiteit te springen.

Het thema was AI Hackathon, dus iedereen liet zijn LLM's op volle toeren draaien. Ik koos voor Claude Code in combinatie met Claude in Chrome. Met claude --chrome kan Claude direct contact leggen met de browser, terwijl je zelf live ziet wat er gebeurt. Dat bleek heel waardevol, vooral bij UI-problemen die unit tests niet vinden.

Opzet en werkwijze

Met Bougie, een tool van mijn collega Jelle Besseling, had ik binnen een paar minuten een Magento omgeving draaiend. Daarna volgden sample data, Hyvä Commerce en de Hyvä theme.

Interessanter is wat ik daarna deed: ik bouwde eerst de AI-context, niet de applicatie. Ik liet een skills/hyva-widgets skill maken op basis van de Hyvä Commerce dashboard-widget documentatie. Dat was achteraf een goede investering. In plaats van Claude telkens opnieuw uit te leggen hoe Hyvä dashboard widgets werken, kreeg Claude project-specifieke instructies en voorbeelden om op terug te vallen.

Eerst specificeren, dan bouwen

Nadat Hyvä Commerce draaide, legde ik Claude uit wat ik wilde maken en hoe ik wilde samenwerken. De eerste echte designprompt was ongeveer:

Ik wil een widget maken die werkt als QuickNav voor users. Navigeren naar settings, product pages, category pages, storeview settings, etc. moet easy en fast zijn. Kinda like configuration panel in Windows or KDE. Icons, search, URL paste, category page, product page, en dan kunnen navigeren naar de category page of producten op die page. Het moet snel zijn en Magento indexes gebruiken waar mogelijk. Het moet "monkey proof" zijn: mijn oma moet het kunnen gebruiken.

Daarna liet ik Claude een design document maken in design/design.md. Na een paar feedbackrondes, plus input van andere developers, ontstond een duidelijke eerste scope: een command-palette achtige zoekervaring voor Magento Admin. Denk aan VS Code, Spotlight of Alfred, maar dan gericht op Magento beheer.

De belangrijkste ontwerpregel was dat het intuïtief moest zijn. Als een niet-technische admin niet binnen een paar seconden begrijpt hoe hij iets vindt, is het ontwerp mislukt.

AI-vriendelijke taakstructuur

Een van de nuttigste keuzes was om het grote specificatiedocument niet direct te laten implementeren. Ik liet Claude het eerst opsplitsen in losse feature-documenten:

design/001-feature-trigger-ui/
design/002-feature-search/
design/003-feature-url-paste/
design/004-feature-category-drilldown/
design/005-feature-scope-awareness/
design/006-feature-access-control/
design/007-feature-search-limiters/
design/008-feature-personalization/
design/009-feature-global-overlay/
design/010-feature-security-audit/

‍
Elke feature ging vervolgens in kleine taken met een vaste naming convention:

feature-complete.md
NNN-feature-requirements.md
NNN-feature-tests.md
steps/NNN-MM-task.md
steps/NNN-MM-task-tests.md
progress.md


Bijvoorbeeld:

001-06-keyboard-navigation.md
001-06-keyboard-navigation-tests.md
002-05-product-provider.md
002-05-product-provider-tests.md


Dat leverde veel kleine bestanden op, maar juist daardoor kon de AI zich per stap focussen. Elke taak had ook testcriteria: hoe een computer dit met unit of integration tests kon controleren, en hoe een mens het in de browser kon verifiëren.

Voor developers is dit misschien wel de belangrijkste les uit de hackathon: de winst zat niet alleen in "AI schrijft code", maar vooral in context-management. Kleine, duidelijke taken voorkomen dat de LLM verdwaalt in een groot feature-idee. Datzelfde gold voor progress.md en memory.md. Toen sessies eindigden, tokens opraakten of de machine opnieuw moest starten, kon Claude verder waar hij gebleven was, met concrete status in plaats van "ongeveer hier ergens".

Wat er gebouwd is

De eerste implementatie probeerde nog een fallback naar oude Magento templating mee te nemen. Dat heb ik eruit laten halen. Het doel was niet om een generieke Magento widget te maken, maar een Hyvä Commerce admin dashboard widget. Door die keuze werd de code lichter en beter te volgen: geen compatibility-laag voor een route die we niet wilden ondersteunen.

Search: snel door bestaande Magento indexen

Een belangrijk performanceprobleem was dat de eerste versie te veel live data probeerde te doorzoeken. Dat voelde niet "snappy" genoeg. Ik gaf Claude daarom de hint om bestaande Magento indexen te gebruiken waar mogelijk.

In de geschiedenis is mooi terug te zien dat ik Claude daar moest terugduwen naar de Magento manier. Op een gegeven moment was Claude aan het over-engineeren, waarna ik aangaf dat een normale Magento indexer logischer was: dagelijks volledig, en incrementeel bij wijzigingen zoals Magento dat zelf ook doet.

Voor categorieën werd bijvoorbeeld gezocht op EAV-waarden zoals naam en url_path, maar productaantallen kwamen uit de bestaande index:

$indexTable = $this->resourceConnection
    ->getTableName('catalog_category_product_index_store' . $storeId);

return $connection->fetchPairs(
    $connection->select()
        ->from($indexTable, ['category_id', 'count' => new \Zend_Db_Expr('COUNT(*)')])
        ->where('category_id IN (?)', $categoryIds)
        ->where('store_id = ?', $storeId)
        ->group('category_id')
);


De tradeoff was acceptabel: producten of pagina's die nog niet geïndexeerd zijn, kunnen ontbreken. Maar voor een quick navigation tool is snelheid belangrijker dan een trage "perfecte" zoekactie. Voor de uitzonderingen bestaat de normale Magento route nog steeds.

Provider-based search

De zoeklaag werd opgebouwd rond providers. Elke provider zoekt in een type data: producten, categorieën, config sections, CMS pages, orders, customers, URL rewrites, search terms, enzovoort. De SearchService doet de fan-out, ranking en limiter-logica:

foreach ($providers as $provider) {
    foreach ($provider->search($searchText, $this->perProviderLimit) as $result) {
        $results[] = $result;
    }
}

usort($results, function (SearchResult $a, SearchResult $b): int {
    if ($a->matchTier !== $b->matchTier) {
        return $a->matchTier <=> $b->matchTier;
    }

    return strcasecmp($a->label, $b->label);
});


Dit maakte de module goed uitbreidbaar. Een nieuwe entiteit toevoegen werd meestal zo gedaan: provider maken, DI configureren, resultaatcontract volgen, tests schrijven.

Search limiters

Tijdens feedback kwam de vraag: wat als ik direct een product wil zoeken? In Magento's standaard admin search kun je wel een product zoeken, maar daarna kom je vaak alsnog op een grid. Eerst moet die grid laden, dan moet de lijst laden, dan klik je het product aan en wacht je opnieuw. QuickNav moest directer zijn.

Daarom kwamen search limiters erbij. Als je query begint met bijvoorbeeld product, category, config, block, page, rewrite, customer of order, wordt alleen die provider gebruikt.

[$firstWord, $remainder] = array_pad(
    preg_split('/\s+/', trim($query), 2),
    2,
    ''
);

if ($remainder === '' || ! isset($this->keywordMap[$keyword])) {
    return null;
}


Dus:

product bag
category bags
page about
rewrite home
customer veronica


De UI toont dat je beperkt zoekt, met een optie om de limiter te verwijderen en alsnog breed te zoeken. Dit bleek een goede middenweg: standaard breed genoeg voor beginners, maar gericht genoeg voor power users.

URL paste handling

Een sterke feature werd URL paste handling. Als je een storefront URL plakt, probeert QuickNav die via url_rewrite te herleiden naar de juiste admin edit page. Een product URL opent direct het product in admin, een categorie URL opent direct de categorie:

http://hackathon.bougie.run:7080/joust-duffle-bag.html
http://hackathon.bougie.run:7080/gear/bags.html


Hier zat een goede UX-regel achter: als iets duidelijk een URL is en binnen de store valt, maar niet gevonden wordt, toon dan een specifieke melding. Niet stilletjes alsnog de hele URL door alle providers halen.

Category drilldown

Categorie-resultaten kregen twee acties: categorie bewerken, en producten in deze categorie bekijken. Magento heeft niet echt een nette, index-backed admin deep link naar dat tweede. De standaard product grid ondersteunt niet simpelweg een category_id filter via URL params, en de category edit page heeft zijn eigen productpaneel. Daarom kwam er een kleine read-only pagina binnen de module zelf:

quicknav/category/products?category_id=N


Die pagina gebruikt een service class voor de query, zodat de logica unit-testbaar blijft zonder Block/ObjectManager gedoe. Ook dit kwam uit een praktische fout: de eerste poging om de logic direct in een Block te stoppen was lastig te testen, omdat Magento Template Blocks intern een ObjectManager bootstrap verwachten.

Scope awareness

Voor configuratie en producten kwam store-view awareness erbij. Bij een config-resultaat kun je direct kiezen tussen Default Config, website scope of store-view scope; producten kunnen direct in store-view mode geopend worden.

Belangrijk detail: die URL's worden server-side met Magento's eigen backend URL builder opgebouwd. Als Magento zelf numerieke website- of store-params verwacht, moet QuickNav niet client-side gaan gokken hoe zo'n URL eruit hoort te zien.

Personalisatie: pinned, recent en most used

Na feedback kwamen ook pinned resultaten, recent gebruikte resultaten en most used bestemmingen erbij. Dit werd bewust client-side met localStorage gedaan, per admin user:

quicknav-pinned-<userId>
quicknav-recents-<userId>
quicknav-usage-<userId>


Ook hier was performance leidend. De live-search dropdown bleef licht en snel; thumbnails werden niet tijdens elke keystroke gerenderd. Product images werden alleen gebruikt in de empty state, waar de pinned en most-used tiles staan.

Global access module

Toen er nog tijd over was, bouwde ik een ondersteunende module: BigBridge_QuickNavAccess. Daarmee werd QuickNav niet alleen beschikbaar op het dashboard, maar ook via een header-icoon en Ctrl+K / Cmd+K op elke admin pagina.

De overlay hergebruikt de bestaande QuickNav widgetlogica. De access module beheert alleen openen, sluiten, focus restore en de keyboard shortcut:

if ((event.ctrlKey || event.metaKey) && key === 'k') {
    event.preventDefault();
    this.open();
}


Na login toont de module eenmalig een hint, zodat gebruikers de shortcut ontdekken zonder documentatie te hoeven lezen.

Wat er live misging

De globale Claude geschiedenis laat goed zien hoe intensief de browser-agent gebruikt werd. In de sessies voor dit project kwamen onder andere deze tool calls voor:

201x mcp__claude-in-chrome__computer
147x mcp__claude-in-chrome__browser_batch
72x  mcp__claude-in-chrome__javascript_tool
39x  mcp__claude-in-chrome__navigate
10x  mcp__claude-in-chrome__read_network_requests
9x   mcp__claude-in-chrome__read_console_messages

‍
Claude schreef dus niet alleen code, maar bestuurde de browser, las console errors, inspecteerde network requests en verifieerde flows end-to-end. Een aantal bugs was zonder browser-test waarschijnlijk veel later gevonden.

De widget crashte het hele dashboard. Bij registratie als Hyvä dashboard widget gebruikte de widget een niet-toegestane Hyvä dashboard categorie, met een ConfigurationMismatchException als gevolg. De oplossing was klein, maar belangrijk: de widget moest een geldige categorie gebruiken.

De input leek niet klikbaar. De widget had aanvankelijk te weinig CSS, waardoor resultaten buiten de dashboard tile vielen en de scroll-overlay van het dashboard pointer events afving.

Getypte tekst liep over het zoekicoon heen. De admin theme CSS had een selector voor inputs in widgets die specifieker was dan onze .quicknav-widget__input. De fix was niet !important, maar de selector natuurlijk specifieker maken via de bestaande DOM-structuur:

.quicknav-widget .quicknav-widget__input-row .quicknav-widget__input {
    padding-left: 2.75rem;
}


De input verloor focus tijdens het typen. De widget moest zijn dashboard block kunnen vergroten als er resultaten waren, maar GridStack's ingebouwde sizeToContent werkte niet goed: Hyvä's widget markup heeft een lege drag handle als eerste child, waardoor GridStack dacht dat de content bijna geen hoogte had. Zelf de GridStack height updaten loste dat op, maar grid.update() verplaatste de DOM node en daardoor verdween de focus. De uiteindelijke fix was om alleen te resizen als de hoogte echt veranderde, en focus plus selectie te herstellen na de resize.

Het QuickNavAccess overlay miste de Alpine factory op niet-dashboard pagina's, waardoor de overlay buiten het dashboard niet werkte.

De search endpoint faalde stil na sessie-rotatie. Die laatste was interessant. De endpoint was eerst een GET endpoint met Magento admin secret key, en die key werd ingebakken in de gecachete widget config. Na sessie-rotatie werd die key stale, waardoor search ineens niets meer deed. De oplossing was om over te stappen op POST met een verse form key uit window.FORM_KEY, vergelijkbaar met hoe Hyvä's eigen dashboard AJAX endpoints werken.

Daarnaast stond het header-icoon van de access module eerst op een eigen regel, omdat de native Magento header icons float: right gebruiken en onze wrapper niet.

Dit soort problemen zijn precies waarom browser-verificatie tijdens AI-development onmisbaar blijft. Claude kan veel bouwen, maar menselijke observatie blijft nodig om te zeggen "dit voelt niet goed".

Security en tests

Een van de belangrijkste delen was de security review.

Eerst was er al een access control feature gebouwd voor de oorspronkelijke providers: config, product, category en store. Maar later kwamen met de search limiters nieuwe providers bij: customers, orders, CMS blocks, CMS pages, URL rewrites en search terms.

Tijdens de security audit bleek dat 6 van de 9 providers geen ACL check hadden:

  • CustomerProvider
  • OrderProvider
  • CmsBlockProvider
  • CmsPageProvider
  • UrlRewriteProvider
  • SearchTermProvider

Dat was een echte bevinding. Een beperkte admin kon via QuickNav mogelijk data zien waar hij via Magento's normale menu geen toegang toe had.

De fix was bewust server-side, in elke provider, voor de query:

if (! $this->authorization->isAllowed(self::ACL_RESOURCE)) {
    return [];
}


Bijvoorbeeld voor CMS pages:

private const ACL_RESOURCE = 'Magento_Cms::page';


En voor categorieën:

private const ACL_RESOURCE = 'Magento_Catalog::categories';


Dit voorkomt dat gevoelige data uberhaupt in de JSON response terechtkomt. Niet verbergen in de template, maar filteren aan de bron.

Ook werd de query-lengte begrensd:

private const MAX_QUERY_LENGTH = 300;


Dat is geen UX-feature, maar hardening. Een admin search box die bij elke keystroke LIKE '%...%' queries kan doen, moet niet onbeperkt lange input door alle providers sturen.

Later zijn meerdere test-admins en rollen gemaakt om ACL's live te controleren, waaronder gebruikers zonder toegang tot customers, orders, CMS blocks, CMS pages, URL rewrites en search terms. Ook dat paste bij de manier van werken: niet alleen unit tests, maar echte admin-rollen gebruiken om te zien of Magento's permissiemodel correct wordt gerespecteerd.

Door het project heen draaide Claude verder unit tests, PHPStan, Pint en Magento setup/compile checks. Op een gegeven moment waren er 144 QuickNav unit tests groen, inclusief nieuwe security tests.

Een praktische testles kwam uit een bug in de category product count. In unit tests was COUNT(*) als integer gemockt, maar in de echte MySQL/PDO response kwam die waarde als numerieke string terug. Daardoor crashte de echte browserflow met een type error, terwijl de unit tests groen waren. De fix was simpel:

productCount: (int) ($productCounts[$categoryId] ?? 0),


Maar de les was belangrijker: mocks moeten de echte driver zoveel mogelijk benaderen. Anders test je een nettere wereld dan productie.

Resultaat

Na twee dagen stond er een snelle QuickNav module waarmee je vanuit Magento Admin direct kunt springen naar producten, categorieën, config sections, CMS pages, CMS blocks, URL rewrites, klanten, orders en search terms. De module bevatte onder andere:

  • live search via providers;
  • search limiters zoals product bag en page about;
  • URL paste handling via url_rewrite;
  • category drilldown naar assigned products;
  • store-view en website scope choices;
  • pinned, recent en most used resultaten;
  • product thumbnails in de empty state;
  • ACL enforcement per provider;
  • een globale Ctrl+K / Cmd+K overlay;
  • een header icon met login hint;
  • unit tests, static analysis en browser-verificatie.

Zelfs de presentatie werd onderdeel van dezelfde AI-loop. In de Claude geschiedenis staat een laatste opdracht:

@presentation/slides/slide-11.html Please test and time these routes,
and update the times with the actual times


Claude moest dus routes testen, tijden meten en slides bijwerken. Uiteindelijk liep ik zelfs tegen Claude session limits aan. Achteraf was dat een goed teken: de AI had niet alleen code gegenereerd, maar twee dagen lang als pair-programmer, tester en documentatieschrijver meegedraaid.

De module werd enthousiast ontvangen, vooral omdat hij een herkenbaar Magento-probleem oplost: je weet vaak wat je zoekt, maar niet waar Magento het verstopt heeft. QuickNav maakt daar een snelle zoek-en-spring ervaring van.

Wat me het meest is bijgebleven, zijn de momenten waarop ik moest ingrijpen. Ik moest Claude soms uit een te complexe richting halen, zoals bij search en indexing. Ik moest ook UX-problemen aanwijzen die technisch "werkten", maar niet goed voelden. AI versnelt, maar het vervangt niet het productgevoel van iemand die weet wat hij wil bouwen.

Bekijk https://github.com/bigbridge-nl/quicknav voor meer informatie over QuickNav en hoe het jouw webshop kan ondersteunen.

Hebben wij jouw
interesse gewekt?

Laten we er eens over kletsen, onder het genot van een goede kop koffie!

neem contact op