On-Demand Class System

How OxyMade loads utility classes into Oxygen only when needed, keeping the builder fast.

OxyMade ships ~1,178 utility classes. Instead of loading all of them into Oxygen’s selector store at once (which slows down the builder), classes are injected on-demand — only when you actually need them. This page covers every scenario, the architecture behind the system, and what to do when things go wrong.

Architecture & Decision Making

The Problem: Vue 2 Reactivity Bottleneck

Oxygen 6 uses Vue 2.7 with Vuex as its state management layer. Every selector in the store is a reactive object — when selectors are added, Vue 2’s reactivity system triggers:

  1. Array mutation observer — detects the new item
  2. CSS rebuild pipeline — regenerates styles for the selector
  3. Deep cloning — copies the entire selector tree for change detection
  4. Watcher cascade — notifies all dependent components

Loading all 1,178 OxyMade selectors into the store causes a chain reaction: each push() call triggers the full pipeline. In benchmarking, a bulk load of all selectors takes several seconds and freezes the builder UI during that time.

The Solution: On-Demand Injection

Instead of bulk-loading all selectors at startup, OxyMade:

  1. Loads a lightweight registry (~50-60 KB) containing only the id, name, and collection of each class — no CSS, no properties
  2. Injects selector stubs into the Vuex store only when a class is actually needed
  3. Serves CSS separately via oxymade-framework.css which is always loaded on every page — so visual rendering is never delayed regardless of which selectors are in the store

This means the builder starts fast (zero OxyMade selectors in store), and classes appear instantly when they’re referenced.

Why Not Store CSS in Selector Properties?

OxyMade selector stubs intentionally contain no CSS properties. This is a deliberate architecture decision:

  • Selector stubs are registration tokens — they tell Oxygen “this class name exists” so the class panel can display it, tokens render as valid, and the class can be assigned to elements
  • Actual CSS lives in oxymade-framework.css — a static file that loads on every page (frontend and builder), independent of the store
  • No cascade interference — because stubs have no CSS properties, their position in the selector array doesn’t affect style specificity or ordering
  • Smaller store footprint — a stub is ~6 fields (id, name, children, locked, collection, type) vs a full selector with breakpoint-specific CSS properties

Why Locked?

Every OxyMade stub is created with locked: true. This prevents users from accidentally opening the class in Oxygen’s selector editor and adding CSS properties to it. Since OxyMade classes get their styles from the framework CSS file, editing the selector properties would have no effect (and could cause confusion).

Batch Injection: Single Reactivity Notification

When multiple classes need injection (e.g., fix button, paste), OxyMade uses push.apply() instead of individual push() calls:

// One reactivity notification for the entire batch
oxySelectors.push.apply(oxySelectors, toAdd);

Vue 2’s array mutation observer fires once for a push.apply() with multiple arguments, regardless of how many items are added. This means injecting 50 classes is as fast as injecting 1.

For autocomplete (single class, user-initiated), a standard push() is used intentionally — the single reactivity cycle (~20ms) is imperceptible to the user and keeps the code simple.

How It Works

The system has two parts:

1. Class Registry (PHP → JavaScript)

When the Oxygen builder loads, OxyMade’s PHP class (BuilderAutocomplete) injects a lightweight class registry into the page as an inline JavaScript variable:

window.OXYMADE_CLASS_REGISTRY = [
  { i: "uuid-1234", n: "m-2", c: "OxyMade Spacing" },
  { i: "uuid-5678", n: "p-4", c: "OxyMade Spacing" },
  // ... ~1,178 entries
];

Each entry is minimal — just id (i), name (n), and collection (c). The source data comes from data/selectors-only.json. At page load, a hash map (registryById) is built for O(1) UUID lookups:

var registryById = {};
for (var ri = 0; ri < classRegistry.length; ri++) {
    registryById[classRegistry[ri].i] = classRegistry[ri];
}

2. Selector Injection (JavaScript → Vuex Store)

When a class is needed, OxyMade creates a selector stub in Oxygen’s Vuex store at store.state.global.oxySelectors:

{
  id: "uuid-1234",
  name: "m-2",
  children: [],
  locked: true,
  collection: "OxyMade Spacing",
  type: "class"
}

The shared injectSelectorsIntoStore(ids) function handles all injection:

  1. Reads the current oxySelectors array from the Vuex store
  2. Builds an existingIds set from all current selectors (deduplication)
  3. For each requested ID, checks if it’s in the registry AND not already in the store
  4. Collects all new stubs into a toAdd array
  5. Pushes all at once via push.apply() (single reactivity notification)
  6. Adds any new collection names to oxySelectorsCollections

This function is used by all five injection pathways.

Important: OxyMade selector stubs are marked locked: true so users don’t accidentally edit them in Oxygen’s class editor. The styles are managed by the framework CSS file.

Five Ways Classes Enter the Store

There are five scenarios where OxyMade classes get injected into the Oxygen store. Each one is automatic — you don’t need to do anything special.

1. Autocomplete Selection

When: You type a class name in Oxygen’s class input field.

Step-by-step flow:

  1. You click on an element in the builder to select it
  2. You navigate to the class input (the text field where you add classes)
  3. You start typing — e.g., m- or flex
  4. After 2 characters, OxyMade’s dropdown appears below the input
  5. The dropdown shows up to 15 matches, grouped by collection (Spacing, Layout, Typography, etc.)
  6. Each class name is prefixed with a purple dot (.) indicating it’s an OxyMade class
  7. Use Arrow keys to navigate, Enter to select, Escape to dismiss
  8. On selection, OxyMade:
    • a. Creates the selector stub in the Vuex store via injectSelectorsIntoStore([entry.i])
    • b. Programmatically sets the class name in Oxygen’s input field using the native value setter
    • c. Dispatches an input event so Oxygen’s Vue binding picks up the change
    • d. After 80ms, dispatches an Enter keydown event to trigger Oxygen’s own addClass logic
    • e. The dropdown closes
  9. The class is now applied to the element — Oxygen sees it as a valid selector

What you see: A dark dropdown with purple accent, grouped by collection. The header says “OxyMade Classes.”

Keyboard shortcuts:

KeyAction
Arrow DownMove to next item
Arrow UpMove to previous item
EnterApply selected class
EscapeClose dropdown

How keyboard interception works: OxyMade registers a capture-phase keydown listener on document. Capture phase fires before Vuetify’s own input handlers, so OxyMade can intercept Arrow/Enter/Escape and call stopImmediatePropagation() to prevent Vuetify’s autocomplete from also processing the key.

Note: The dropdown only appears when autocomplete is enabled (default: on). You can disable it in admin settings without affecting other on-demand features.

2. Fix Button (Builder Toolbar)

When: You open a page that already has OxyMade classes applied to elements, but the selectors aren’t in the store (e.g., after deleting selectors from admin, or on first load of an existing page).

Step-by-step flow:

  1. Look for the wrench icon (purple, #a78bfa) in the builder toolbar, next to the OxyMade button
  2. Click it
  3. OxyMade scans the Vuex store for the document’s exportedLookupTable (a flat hash map of all elements in the current page)
  4. For each element, reads node.data.properties.meta.classes — an array of UUID strings
  5. Filters to only OxyMade UUIDs (exists in registryById)
  6. Passes all collected UUIDs to injectSelectorsIntoStore(ids) — batch injection
  7. A toast notification shows the result

What you see:

  • “Fixed 12 classes (5 already loaded)” — if classes were missing and got restored
  • “All classes already loaded” — if everything was already in the store

The icon changes color temporarily:

  • Green (#22c55e) — classes were fixed
  • Gray (#64748b) — everything was already loaded
  • Returns to purple after 2 seconds

When to use it:

  • After deleting OxyMade selectors from admin settings
  • After syncing selectors and re-entering the builder
  • If you notice class names showing as “deleted” tokens
  • As a general “make sure everything works” check

3. Invalid Token Auto-Fix

When: Oxygen shows a class as a red/deleted token because its selector isn’t in the store.

Step-by-step flow:

  1. You select an element that has OxyMade classes applied
  2. Oxygen renders the class tokens in the class panel
  3. If a class selector is missing from the store, Oxygen renders the token with the CSS class .oxy-class-token--invalid and a data-id attribute containing the UUID
  4. OxyMade has a MutationObserver watching the #app element (or document.body) for DOM changes
  5. On every DOM mutation, OxyMade queries for .oxy-class-token--invalid[data-id] elements
  6. For each invalid token, reads the data-id attribute (the UUID)
  7. Checks if that UUID exists in registryById
  8. If yes, passes it to injectSelectorsIntoStore(ids)
  9. Oxygen re-renders the token as valid (the MutationObserver detects the DOM update, but the token is now valid so no further action is needed)

What you see: Invalid tokens briefly flash as deleted, then immediately become valid. This happens so fast you usually won’t notice it.

How the MutationObserver works: A single observer watches for childList and subtree changes on the #app element. The same observer also detects when new class input fields appear (for autocomplete attachment). It runs on every DOM mutation, but the queries (querySelectorAll) are fast and bail early when nothing is found.

Note: This only fixes OxyMade classes. If you see invalid tokens for non-OxyMade classes, those need to be handled through Oxygen’s own tools.

4. Paste Detection

When: You copy an element (or element tree) that has OxyMade classes and paste it into the same or different page.

Step-by-step flow:

  1. You copy an element in the Oxygen builder (Ctrl/Cmd + C)
  2. You paste it (Ctrl/Cmd + V) — on the same page or a different page
  3. Oxygen fires a Vuex mutation: document/pasteCopiedElementMutation
  4. OxyMade’s store.subscribe() callback detects this specific mutation type
  5. It reads mutation.payload.element — the root of the pasted element tree
  6. It recursively walks the tree via extractClassIdsFromElement():
    • Reads el.data.properties.meta.classes from each element
    • Filters to OxyMade UUIDs (exists in registryById)
    • Recurses into el.children
  7. All collected UUIDs are passed to injectSelectorsIntoStore(ids) — batch injection
  8. A toast notification confirms the result

What you see: A toast notification: “Pasted — loaded 8 classes (3 already loaded)”

Nested elements: The scan is fully recursive. If you paste a section containing a container with a heading and a button, all OxyMade classes on every element in that tree are detected and injected.

How store.subscribe() works: Vuex’s subscribe() fires after a mutation commits but before Vue’s nextTick render cycle. This means OxyMade injects the missing selectors before the UI updates, so the pasted elements never flash as invalid.

Important: This uses Oxygen’s internal Vuex mutation system, not the browser clipboard. It works for Oxygen’s copy/paste operations, not for pasting from external sources. For pasting design JSON from external sources, see the Paste Import feature.

5. Fix Styling (Admin Settings)

When: You need to restore OxyMade selectors across your entire site — all pages, templates, headers, footers, and blocks. This is a server-side operation.

Step-by-step flow:

  1. Go to WordPress Admin → OxyMade Settings → Step 5 (Selectors)
  2. Click the “Fix Styling” button (purple, with wrench icon)
  3. The button shows a spinning animation and “Scanning…” text
  4. On the server, the AJAX handler (fix_styling()) runs:
    • a. Builds a registry from data/selectors-only.json — hash map of UUID → selector stub
    • b. Determines the meta key — checks for __bdox('_meta_prefix') (Breakdance-in-Oxygen mode), falls back to _oxygen_data
    • c. Queries all posts with builder data (meta_query for the meta key) + all Oxygen CPTs (oxygen_template, oxygen_header, oxygen_footer, oxygen_block)
    • d. For each post, reads get_post_meta($id, $meta_key, true), decodes the JSON wrapper, then decodes tree_json_string for the actual element tree
    • e. Walks the tree — uses exportedLookupTable for flat iteration when available (faster), falls back to recursive root.children walk
    • f. Collects all class UUIDs from node.data.properties.meta.classes
    • g. Filters to OxyMade UUIDsarray_intersect_key() with the registry
    • h. Reads current selectors from oxygen_oxy_selectors_json_string option
    • i. Deduplicates — builds an existing ID set, skips already-present selectors
    • j. Adds missing stubs — creates selector stubs matching the registry format
    • k. Saves via update_option() with _invalidate_selectors_cache() called before and after
    • l. Updates collections — adds any new collection names to oxySelectorsCollections
  5. The response shows: pages scanned, classes found, classes added, classes skipped
  6. If any classes were added, the page auto-reloads after 2 seconds

What you see: A success notice: “Done! Scanned 42 pages. Found 156 OxyMade classes. Added 89, skipped 67 (already loaded).”

Why _invalidate_selectors_cache() is called twice: OxyMade uses a pre_option filter for transient caching of the selectors option. During update_option(), WordPress internally calls get_option() — which hits the pre_option filter and returns stale cached data. Calling _invalidate_selectors_cache() before the write ensures update_option reads the real DB value. Calling it after ensures subsequent reads see the updated data (the filter would otherwise re-cache the old value during the write).

Performance on large sites: The handler calls set_time_limit(300) (5 minutes). Posts are queried with fields => 'ids' to minimize memory. Each tree is fetched individually via get_post_meta(). The AJAX request has a 300-second timeout on the JS side.

When to use it:

  • After accidentally deleting all selectors
  • After migrating a site or restoring from backup
  • After updating the plugin if classes seem broken across multiple pages
  • When you want to ensure every page’s OxyMade classes are properly registered

Note: This is a server-side operation. It doesn’t require the builder to be open. It writes directly to Oxygen’s oxygen_oxy_selectors_json_string option in the database.

Delete Modal Improvements

The delete selectors UI was redesigned in 1.4.0 with a modal dialog that gives you precise control over what gets deleted.

Opening the Modal

  1. Go to OxyMade Settings → Step 5 (Selectors)
  2. Click the red “Delete” button
  3. The modal opens with a backdrop overlay

The modal first fetches current selector counts via an AJAX call to oxymade_get_selector_count, then displays:

  • Total Selectors: All selectors in Oxygen’s store
  • OxyMade Selectors: Selectors that match OxyMade’s UUID registry
  • Other Selectors: Non-OxyMade selectors (user-created, third-party, etc.)
  • Custom Selectors: Selectors whose name starts with “custom” (case-insensitive)
  • Components: OxyMade component selectors (headings, buttons, cards, etc.)

Two Protective Toggles

The modal header has two toggle switches that control what gets preserved during deletion:

ToggleDefaultWhat it does
Keep componentsONWhen ON, preserves OxyMade component selectors (h1-h6, button variants, card variants, button-pair). These have actual CSS properties unlike utility class stubs.
Include custom*OFFWhen OFF, preserves all selectors whose name starts with “custom” (case-insensitive). When ON, these are included in the deletion.

How component detection works: OxyMade maintains a hardcoded list of 30 component UUIDs in get_component_ids(). These are headings (h1-h6), button variants (btn-s, btn-m, btn-l, btn-primary, btn-secondary, btn-accent, btn-tertiary, btn-white, btn-light, btn-link, btn-outline, btn-black), card variants (card-normal, card-loose, card-tight, card-snug, card-relaxed, card-none), and layout components (hero, button-pair). These selectors have actual CSS properties and are treated differently from utility class stubs.

How custom detection works: The delete handler checks stripos($selector['name'], 'custom') === 0 — case-insensitive match for names starting with “custom”. This catches classes like custom-header, custom-card-dark, etc. that users typically create for project-specific styling.

Dynamic Button Counts

When you change the toggles, the button counts update in real-time:

  • “Delete OxyMade (847)” — shows how many OxyMade selectors will be deleted (minus protected ones)
  • “Delete All (1,203)” — shows how many total selectors will be deleted (minus protected ones)

The descriptions also update dynamically:

  • With both toggles active: “Removes OxyMade utility selectors, keeps other selectors and custom* and components selectors intact.”
  • With no toggles: “Removes all OxyMade selectors including custom* and components, keeps other selectors intact.”

Two Delete Actions

ButtonColorWhat it deletes
Delete OxyMadeAmberOnly OxyMade selectors (identified by UUID match against registry). Keeps all non-OxyMade selectors.
Delete AllRedALL selectors in Oxygen’s store. Only exception: selectors protected by the current toggle settings.

Both actions respect the toggle states and call _invalidate_selectors_cache() before and after the update_option() write.

Recovery After Deletion

After deleting selectors, your pages still look correct on the frontend (CSS comes from oxymade-framework.css, not from selectors). To restore the selectors for builder use:

  1. Click “Fix Styling” — scans all pages and re-adds only the selectors that are actually used
  2. Open individual pages in the builder and click the wrench icon for per-page fixing

Admin Settings

Autocomplete Toggle

You can enable or disable the autocomplete dropdown in the builder:

  1. Go to WordPress Admin → OxyMade Settings → Step 5
  2. Find the “Autocomplete” toggle in the header row
  3. Toggle it off to disable the dropdown suggestions
  4. The change saves immediately via AJAX (no page reload needed)
  5. The change takes effect on next builder page load

What gets disabled: Only the autocomplete dropdown and its keyboard interception. The fix button, paste detection, and invalid token auto-fix continue to work normally.

Technical detail: The toggle state is passed to the builder as window.OXYMADE_AUTOCOMPLETE_ENABLED. The JavaScript checks this value and skips attachAutocomplete() and the capture-phase keydown listener when disabled. The MutationObserver still runs (needed for invalid token detection), and the fix button still renders in the toolbar.

WordPress boolean quirk: The toggle uses get_option('oxymade_autocomplete_enabled', '1') with explicit string comparison. WordPress stores checkbox values as '1' or '0' strings, not boolean true/false. The check !in_array(get_option(...), ['0', '', false], true) handles all edge cases including empty string and false.

Common Workflows

Adding a New Class (Full Flow)

  1. Select an element in the Oxygen builder
  2. Click the class input field (or wherever you add classes)
  3. Start typing — e.g., flex to find flexbox classes
  4. The OxyMade dropdown appears showing matches: flex, flex-wrap, flex-1, etc.
  5. Arrow down to flex and press Enter (or click it)
  6. Behind the scenes:
    • OxyMade creates a stub selector {id: "...", name: "flex", locked: true, ...} in the Vuex store
    • OxyMade sets the input value to “flex” and dispatches Enter
    • Oxygen’s own addClass logic runs — it finds the “flex” selector in the store and assigns its UUID to the element’s meta.classes array
  7. The element now has the flex class — visible in the class panel and active in the builder
  8. The CSS from oxymade-framework.css applies immediately (it was already loaded)

Pasting a Block with OxyMade Classes

  1. On Page A, you have a pricing card section using OxyMade classes: flex, p-6, rounded-lg, shadow-md, text-center
  2. Copy the section (Ctrl/Cmd + C)
  3. Navigate to Page B in the builder
  4. Paste (Ctrl/Cmd + V)
  5. Oxygen fires the document/pasteCopiedElementMutation Vuex mutation
  6. OxyMade’s subscriber intercepts it, walks the pasted tree:
    • Section → finds flex, p-6
    • Card container → finds rounded-lg, shadow-md
    • Text → finds text-center
  7. All 5 UUIDs are passed to injectSelectorsIntoStore() in a single batch
  8. Toast: “Pasted — loaded 5 classes”
  9. The pasted section renders correctly — all classes work immediately

Starting Fresh with OxyMade

  1. Install and activate OxyMade
  2. Complete the setup wizard (Steps 1-5)
  3. Open any page in the Oxygen builder
  4. Start typing class names — the autocomplete dropdown will guide you
  5. Select classes from the dropdown to apply them

No manual sync needed. Classes are loaded one at a time as you use them.

Migrating a Site

  1. Export your site (using your preferred migration tool)
  2. Import on the new server
  3. Install and activate OxyMade on the new server
  4. Go to OxyMade Settings → Step 5
  5. Click “Fix Styling” to scan all pages and restore used selectors
  6. Open the builder to verify — all classes should work

Recovering After Deleting Selectors

If you accidentally deleted OxyMade selectors and pages look broken in the builder:

  1. Go to OxyMade Settings → Step 5
  2. Click “Fix Styling” — this scans all pages and re-adds used selectors
  3. Open individual pages in the builder
  4. If any classes still show as invalid, click the wrench icon in the toolbar

Note: Frontend pages still render correctly after deleting selectors — the CSS comes from oxymade-framework.css, not from the selectors. Only the builder UI (class panel, tokens) is affected.

Sharing Elements Between Pages

  1. On Page A, copy an element that uses OxyMade classes (Ctrl/Cmd + C)
  2. Navigate to Page B in the builder
  3. Paste the element (Ctrl/Cmd + V)
  4. OxyMade automatically detects the paste and loads any missing classes
  5. You’ll see a toast confirming how many classes were loaded

Checking What’s Loaded

To see which OxyMade classes are currently in the store:

  1. Open the builder
  2. Click the wrench icon in the toolbar
  3. If it says “All classes already loaded” — everything is fine
  4. If it says “Fixed N classes” — those classes were missing and are now restored

How CSS Works

Understanding the CSS architecture helps explain why on-demand loading works seamlessly:

LayerWhat it doesWhen it loads
oxymade-framework.cssContains all ~1,178 class styles with .oxygen prefix for 0,2,0 specificityAlways (page load, frontend + builder)
Selector stubs in VuexTell Oxygen “this class exists”On-demand (when needed)
Oxygen’s generated CSSOxygen’s own styles from selector propertiesReal-time (builder)

The framework CSS is always present, so elements always render correctly — both on the frontend and in the builder. The selector stubs only exist so Oxygen’s class panel can recognize the class names. If a stub is missing:

  • The element still looks correct (CSS is from the framework file)
  • But the class shows as “deleted” in the class panel
  • You can’t assign that class to new elements via Oxygen’s UI (until the stub is injected)

Key insight: Removing an OxyMade selector from the store doesn’t change how the page looks. The CSS is served from the framework file, not from the selector’s properties. The selector is just a “registration” that lets Oxygen’s UI know the class exists.

CSS Specificity (1.3.0+)

Starting in version 1.3.0, all OxyMade utility classes are prefixed with .oxygen for 0,2,0 specificity:

.oxygen.m-2 { margin: 0.5rem; }
.oxygen.flex { display: flex; width: 100%; }

This reliably beats Oxygen’s own compound selectors (typically 0,1,0) without needing !important. The framework CSS loads after Oxygen’s native CSS for correct source order.

All Changes: 1.3.0 → 1.4.0

Version 1.3.0 (February 2026)

CSS Specificity Upgrade:

  • All utility classes prefixed with .oxygen for 0,2,0 specificity
  • Removed all !important declarations from framework CSS — no longer needed with specificity prefix
  • Framework CSS now loads after Oxygen’s native CSS for correct source order
  • Added text-align: center to items-center class to match Oxygen builder behavior

Flexbox & Layout Fixes:

  • Removed display: flex from direction-only classes (horizontal, vertical, flex-wrap, and all breakpoint variants) — direction classes no longer force elements visible
  • Removed display: flex from justify-content, align-items, and align-content utility classes — these now only set their specific property
  • Removed duplicate gap, col-gap, and row-gap classes across collections
  • .flex and .grid utility classes include width: 100% for consistent full-width container behavior
  • Section spacing classes now target > .section-container for correct padding inheritance

New Features:

  • OxyMade Button — No Vertical Padding toggle for text-link style
  • Base white and black colors added to right-click color picker
  • Added mb-auto and mr-auto margin utility classes

Fixes:

  • Spacing context modal now opens for space-between, space-above, space-below, and slide spacing controls
  • Input picker no longer closes parent popover when clicking inside picker
  • OxyMade Button default properties set to Sign up, primary color, medium size, small radius

Version 1.4.0 (March 2026)

On-Demand Class System (New):

  • Classes are injected into Oxygen’s Vuex store only when needed, not bulk-synced at startup
  • Lightweight class registry (~50-60 KB inline) with O(1) UUID lookups via hash map
  • Shared injectSelectorsIntoStore(ids) function used by all injection pathways
  • Single push.apply() batch injection for Vue 2 reactivity efficiency

Custom Autocomplete (New):

  • Dedicated OxyMade class dropdown in the builder class input
  • Keyboard navigation with capture-phase keydown interception (fires before Vuetify)
  • Shows up to 15 matches after 2 characters, grouped by collection
  • Admin toggle to enable/disable autocomplete independently of other on-demand features

Fix Button in Builder Toolbar (New):

  • Wrench icon in toolbar, next to OxyMade button
  • Scans current document’s exportedLookupTable for all class UUIDs
  • Batch-injects missing OxyMade selectors
  • Visual feedback via icon color change and toast notification

Paste Detection (New):

  • store.subscribe() watches for document/pasteCopiedElementMutation Vuex mutation
  • Recursively walks pasted element tree to extract all class UUIDs
  • Batch-injects missing selectors before Vue’s render cycle
  • Toast notification confirms number of classes loaded

Invalid Token Auto-Fix (New):

  • MutationObserver detects .oxy-class-token--invalid[data-id] elements
  • Looks up UUID in registry and injects matching selectors
  • Tokens fix themselves immediately without user intervention

Fix Styling Admin Button (New):

  • Server-side scan of ALL content (pages, posts, templates, headers, footers, blocks, CPTs)
  • Queries posts with _oxygen_data meta key + all Oxygen CPTs
  • Walks each post’s element tree, collects class UUIDs, filters to OxyMade
  • Deduplicates against existing selectors, adds missing stubs
  • Cache invalidation before and after update_option() writes

Delete Modal with Toggles (New):

  • Modal shows current selector counts (total, OxyMade, other, custom, components)
  • “Keep components” toggle (default: ON) — preserves 30 component selectors (headings, buttons, cards)
  • “Include custom*” toggle (default: OFF) — controls whether custom-prefixed selectors are deleted
  • Dynamic button counts update when toggles change
  • Two delete actions: “Delete OxyMade” (only OxyMade selectors) and “Delete All” (all selectors)

Fixes:

  • Fixed selector delete operations not persisting due to transient cache returning stale data
  • Fixed Fix Styling reporting classes added but nothing written to DB (cache invalidation ordering)
  • Fixed Fix Styling finding 0 classes due to double-encoded post meta (tree_json_string)
  • Fixed autocomplete toggle not disabling suggestions (WordPress boolean storage quirk)
  • Fixed PHP 8 fatal error when variables.json is missing or corrupt (foreach on null)

Security:

  • Fixed nonce verification bypass in PasteCSSHandler — empty nonce no longer skips check
  • Added _invalidate_selectors_cache() helper for safe option writes with transient caching

Troubleshooting

Classes showing as “deleted” tokens

Cause: The selector stubs for those classes aren’t in Oxygen’s store.

Fix:

  1. Click the wrench icon in the toolbar — this fixes all classes on the current page
  2. If the issue persists across many pages, use Fix Styling in admin settings

Autocomplete dropdown not appearing

Check these:

  1. Is autocomplete enabled? Go to OxyMade Settings → Step 5 and check the toggle
  2. Are you typing at least 2 characters?
  3. Does the class name match something in OxyMade’s registry? Try common prefixes like m-, p-, flex, grid, text-

Autocomplete appears but class doesn’t apply

Cause: Oxygen’s class input may not be receiving the programmatic input event.

Fix:

  1. Try clicking the class name in the dropdown instead of using Enter
  2. Reload the builder page and try again
  3. If persistent, manually type the full class name and press Enter

Fix Styling says “Added 0”

Cause: All used classes are already in the store.

This is normal — it means no classes were missing. If pages still look wrong, the issue may be with the framework CSS file, not the selectors.

Fix Styling says “Scanned 0 pages”

Cause: No pages have Oxygen builder data.

Check:

  1. Ensure Oxygen is active and pages have been edited with the builder
  2. Verify the pages are saved (not just drafts with no builder data)

Toast notifications not appearing

Cause: Another plugin’s CSS may be hiding the toast with z-index conflicts.

This doesn’t affect functionality — the classes are still being injected even if you can’t see the notification.

Classes work in builder but not on frontend

Cause: The oxymade-framework.css file may not be loading on the frontend.

Check:

  1. View page source and search for oxymade-framework.css
  2. If missing, go to OxyMade Settings and verify selectors are synced (Step 5)
  3. Clear any caching plugins

Technical Details

For developers who want to understand the internals:

Class Registry Format

The registry is injected as window.OXYMADE_CLASS_REGISTRY:

[
  { i: "a1b2c3d4-...", n: "m-2", c: "OxyMade Spacing" },
  { i: "e5f6g7h8-...", n: "flex", c: "OxyMade Layout" },
  // ...
]
FieldDescription
iUUID — unique identifier matching Oxygen’s selector ID
nClass name (e.g., m-2, p-4, flex)
cCollection name for grouping in the UI

Selector Stub Format

What gets injected into store.state.global.oxySelectors:

{
  id: "a1b2c3d4-...",
  name: "m-2",
  children: [],
  locked: true,
  collection: "OxyMade Spacing",
  type: "class"
}

No properties field — the stub has no CSS. This is intentional. Oxygen uses the id and name to associate the class with elements.

Element Class References

Elements store class references as UUID arrays:

element.data.properties.meta.classes = [
  "a1b2c3d4-...",  // m-2
  "e5f6g7h8-...",  // flex
]

These UUIDs are what the fix button, paste detection, and Fix Styling scan for.

Performance

OperationTimeImpact
Single class injection (autocomplete)~20msOne Vue reactivity cycle
Batch injection (fix/paste)~5ms totalSingle push.apply() — one reactivity cycle
Registry lookupO(1)Hash map by UUID
Full page scan (fix button)~2msIterates exportedLookupTable
Fix Styling (server)1-5sDepends on number of pages

Source Files

FilePurpose
assets/js/builder-autocomplete.jsAll 5 client-side injection mechanisms
includes/class-builder-autocomplete.phpInjects class registry + script, transient caching
data/selectors-only.jsonSource data for the class registry
includes/admin-settings.phpFix Styling handler, delete handlers, admin UI