Primix Troubleshooting
Common symptoms, root causes, and fixes for the Primix admin panel framework.
1. Builder iframe blank
Symptom: The visual builder canvas loads but the iframe stays white or shows a 404 console error.
Cause: Tagixo's published assets are missing or stale under public/vendor/tagixo/. The iframe sources builder.js and related chunks from that directory — Vite is not involved in serving them.
Fix:
php artisan vendor:publish --tag=tagixo-assets --force
Re-run this after every plugin version bump. Commit the updated public/vendor/tagixo/ directory — the production Dockerfile runs composer install --no-scripts so it cannot auto-publish at build time.
Note: Do not add a Vite alias or HMR entry for builder assets. They are static vendor files, not part of the app's JS bundle.
2. Save silently fails
Symptom: Clicking "Save" in the builder or a Primix resource produces no error but the change is not persisted.
Cause: A catch block somewhere in the save pipeline swallows the exception and returns a truthy response. The frontend receives a 200 and assumes success.
Fix: saveStructure() (and any method it delegates to) must let exceptions propagate. Use the tagixo:save-error event to surface failures to the UI:
// WRONG — swallows the exception
try {
$this->saveStructure($payload);
} catch (\Exception $e) {
// silent
}
// CORRECT — let it propagate; the framework emits tagixo:save-error
$this->saveStructure($payload);
The InteractsWithVisualBuilder trait must NOT wrap its internal calls in a bare catch. If you need to handle a specific error (e.g. validation), re-throw any \Exception you did not explicitly expect.
Warning: Never add a catch-all inside InteractsWithVisualBuilder methods — it is the wrong scope. Handle errors at the controller level only.
3. Modules palette empty
Symptom: The "Add module" palette in the builder shows no modules (or only the built-in ones).
Cause: A context mismatch between the module's registered context and the active builder context. Module registration uses an exact-string comparison — a module registered for "page" will not appear in a "mail" builder and vice versa.
Context reference:
| Builder context string | Used for |
|---|---|
page |
Standard public pages |
mail |
Email template builder |
popup |
Popup builder |
Fix: Verify that each custom module is registered with the correct context string:
// Registers only for the page builder
TgxModule::register('my-module', MyModule::class, context: 'page');
// Registers for all contexts
TgxModule::register('my-module', MyModule::class);
If the context argument is omitted, the module appears in all contexts. If you pass an incorrect string, the module is silently excluded.
4. LiVue component not found
Symptom: A Vue component used inside a Primix (LiVue-powered) page throws [Vue warn]: Failed to resolve component: my-component.
Cause: The component was not registered with the LiVue plugin before mount. LiVue manages its own Vue app instance — components registered on a separate app instance (e.g. a Livewire-side global) are not visible inside the LiVue context.
Important: LiVue and Livewire are separate systems. Do not confuse Livewire's Alpine/JS bridge with LiVue's Vue 3 app.
Fix: Register the component via the LiVue plugin API before the app boots:
// resources/js/app.js (or equivalent LiVue entry)
import MyComponent from './components/MyComponent.vue'
livue.component('my-component', MyComponent)
If the component is in a package, ensure the package's service provider calls LiVue::component() in its boot() method and that the provider is loaded before the page renders.
5. CSS stripped after LiVue morph
Symptom: Custom styles injected into the <head> disappear after navigating between LiVue pages (soft navigation / morphing).
Cause: LiVue's morph algorithm replaces the <head> diff, removing <style> tags that were injected dynamically by JavaScript after the initial load.
Fix: Use PrimixFormStyles::scriptFrom() to inject styles as an inline <script> that re-applies itself on every morph cycle:
use Primix\Support\PrimixFormStyles;
// In a Blade view or render hook:
{!! PrimixFormStyles::scriptFrom($css) !!}
This emits a <script> block that injects the CSS into <head> and re-runs after each LiVue navigation. Do not use a plain <style> tag or a bare document.head.appendChild() call — both will be stripped on the next morph.
6. Media gallery not opening
Symptom: Clicking a media picker field in a Primix form does nothing; the gallery modal never appears.
Cause: The media gallery is an opt-in feature. Unless withMediaGallery() is called on the Primix panel or resource, the necessary JS/modal is not booted.
Fix: Call withMediaGallery() when defining the panel:
// app/Providers/AdminPanelProvider.php
use Primix\Panel;
Panel::make()
->withMediaGallery()
// ...
Or, if the gallery should only be available on specific resources, call it on the resource's panel() configuration. Without this opt-in, MediaGalleryPickerField renders a button that has no bound event listener.
7. Builder page 403
Symptom: Navigating to a builder page (e.g. /admin/pages/1/edit) returns a 403 Forbidden.
Possible causes and fixes:
a) Policy / canAccess() returning false
Primix evaluates canAccess() on the resource. Check the resource class:
public static function canAccess(): bool
{
return auth()->user()?->isAdmin() ?? false;
}
b) Guard mismatch
If the admin panel uses the admin guard but the user authenticated via the web guard, auth()->user() returns null inside Primix. Ensure the panel guard and the login form guard match.
c) Stale route cache A cached route may point to an old controller or middleware. Clear it:
php artisan route:clear
php artisan optimize:clear
d) Octane cached state With Octane (Swoole), middleware and service providers can carry stale state between requests. After any policy or provider change:
php artisan octane:reload
8. App form modules not showing
Symptom: Custom form modules registered by the application do not appear in the form builder palette.
Cause: enableAppForms() is being called before the Tagixo plugin finishes booting, or it is called more than once (the second call resets the registry).
Fix: Call enableAppForms() inside a booted callback, after all service providers have run:
// app/Providers/AppServiceProvider.php
use Ccast\Tagixo\Facades\Tagixo;
public function boot(): void
{
$this->app->booted(function () {
Tagixo::enableAppForms();
Tagixo::registerFormModule('my-form-module', MyFormModule::class);
});
}
Do not call registerFormModule() before enableAppForms() — modules registered before the app forms registry is initialised will be silently discarded.
Do not call enableAppForms() twice (e.g. once in AppServiceProvider and once in AdminPanelProvider) — the second call resets the registry and discards all previously registered modules.
9. Notifications not showing
Symptom: A $dispatch('notify', ...) call inside a Primix Livewire component produces no visible notification.
Cause: Primix uses LiVue's PrimixNotification system, not Filament's $dispatch('notify') event. The two systems share a similar API surface but listen on different event buses.
Fix: Use PrimixNotification::dispatch() (server-side) or the LiVue notification composable (client-side):
// Server-side (from a Livewire component or action)
use Primix\Notifications\PrimixNotification;
PrimixNotification::dispatch('Record saved.', type: 'success');
// Client-side (inside a LiVue component)
import { useNotification } from '@primix/support'
const notify = useNotification()
notify.success('Record saved.')
Do not use Filament's $this->notify() or $dispatch('notify') in a Primix context — Filament's notification listener is not present unless the Filament panel provider is also loaded.
SDK version note: PrimixNotification::dispatch() was added in primix/primix 1.1.x. If the method is not found, update the package.