# Kutby Foundation Audit Report

**Date:** 2026-07-24  
**Scope:** Full Foundation review against binding contracts  
**Contracts:** `ARCHITECTURE.md` · `PERFORMANCE.md` · `DESIGN_SYSTEM.md`  
**Mode:** Read-only — **no code was modified**

This report evaluates Kutby as an enterprise product expected to evolve for 10+ years. Issues are real, evidence-based, and actionable. Style nits and speculative future problems are excluded.

---

## Executive summary

Foundation 2.5 is a **credible, coherent architecture**. Boot is thin, Module Manager is real, Theme Options storage is Manager-gated, there is **no runtime remote HTTP**, and Admin schema panels correctly compose Components + Field renderers.

The largest gaps are not “missing Foundation,” but **incomplete adoption** and **known dual paths**:

1. Legacy Admin shell CSS still ships hardcoded design values through Vite.  
2. Modal/overlay accessibility is incomplete vs Design System dialog rules.  
3. Orphaned Page classes retain parallel English field maps.  
4. Several custom Admin pages and frontend empty states bypass Components / empty-state standards.  
5. A few performance patterns (font preload path mismatch, always-on Admin media/color assets, unmemoized SVG reads) create avoidable cost.

**Overall Foundation readiness: 78 / 100** — stable enough to begin frontend design work, with a clear remediation backlog before commercial polish.

---

# 1. Strengths

### Architecture

- `functions.php` is bootstrap-only (constants → Autoloader → `Theme::boot()`).
- `Theme` only runs `ModuleManager` and emits boot events.
- `ModuleManager` implements register → boot, filters, and events.
- Modules do **not** import other Modules (no cross-module coupling found).
- PSR-4 `Kutby\` → `inc/` is consistent.
- Theme Options **storage** uses `get_option` / `update_option` **only** inside `Manager`.
- Save path validates via `FieldRegistry` before `Manager::save()` (form + AJAX).
- `AbstractPage` + `SchemaDrivenPage` + `PageFactory` is a sound Options UI stack.
- Events bus (`Dispatcher`) is thin and correctly namespaced.
- Asset Manifest Manager / Manifest request-cache `json_decode` once.

### Performance

- **Zero** `wp_remote_get` / `wp_remote_post` in theme PHP.
- **No** runtime directory scans (`scandir` / recursive iterators) in theme PHP.
- Manager request cache for options; `update_option( …, false )` avoids autoload bloat.
- Frontend scripts deferred; Vite production path preferred.
- Admin assets gated to Kutby screens (not all of wp-admin).
- No frontend jQuery; local Tajawal; SVG icons.
- Performance / SEO modules avoid query-in-loop patterns.
- Showcase gated behind debug/dev flags.

### Design System

- Admin Components API is broad (panel, card, button, fields, notice, modal, skeleton, loader, tabs, etc.).
- Schema-driven panels use Components + Notices.
- Control Center shell is `dir="rtl" lang="ar"`.
- Sidebar uses SVG Icon system (Dashicons only for WP menu icon — allowed).
- Theme Admin forms use `kutby-btn`, not native WP `.button`.
- `admin-components.css` is largely token-driven.
- Semantic notice tokens exist and are used by Notices.

### Theme Options

- Schema is the definition source; Field Registry is the intended API (partially adopted).
- Validation rule set is real (required, numeric, integer, boolean, email, url, enum, min, max, callback).
- Sanitization remains on save.
- Tokens bridge Theme Options → CSS variables.
- Live SettingsBridge stubs exist for future AJAX/CSS regen (preview not implemented — intentional).

---

# 2. Violations

Each issue: **Severity · Location · Problem · Contract · Recommended solution · Priority**

---

### V-01 — Critical

| | |
|---|---|
| **Severity** | Critical |
| **Location** | `assets/css/admin/admin.css` (imported by `src/css/admin.css` → production `assets/dist/admin.min.*.css`) |
| **Problem** | Admin visual shell still hardcodes hex colors, raw px spacing/typography, and physical LTR-oriented rules. This CSS **ships** in production Admin bundles. |
| **Why** | `DESIGN_SYSTEM.md` §3/§4/§15: feature CSS must not hardcode colors/spacing; Dark Mode readiness requires tokens. |
| **Solution** | Migrate shell styles to token variables (or rebuild shell on top of `admin-components.css`); remove invented sizes; prefer logical properties; rebuild Vite admin entry. |
| **Priority** | P0 |

---

### V-02 — Critical

| | |
|---|---|
| **Severity** | Critical |
| **Location** | `inc/Admin/UI/Components.php` (`modal`); `assets/js/admin.js` |
| **Problem** | Modal open/close lacks focus trap, Escape-to-close, focus restore, and safe backdrop dismiss rules. |
| **Why** | `DESIGN_SYSTEM.md` §10 Accessibility, §26 Dialog Principles. |
| **Solution** | Implement focus trap + Escape + restore; apply §26 backdrop rules; cover in UI Showcase. |
| **Priority** | P0 |

---

### V-03 — High

| | |
|---|---|
| **Severity** | High |
| **Location** | `inc/Performance/Performance.php` (`preload_fonts`); default `preload_fonts` option |
| **Problem** | Preload targets `assets/fonts/tajawal/*.woff2` while production CSS serves hashed fonts from `assets/dist/`, risking **double font downloads**. |
| **Why** | `PERFORMANCE.md` §2 fonts / §9 webfont budget / “every kilobyte matters.” |
| **Solution** | Preload manifest/hashed font URLs, or disable preload by default until paths match CSS. |
| **Priority** | P1 |

---

### V-04 — High

| | |
|---|---|
| **Severity** | High |
| **Location** | `inc/Pages/HeaderPage.php`, `FooterPage.php`, `HomepagePage.php`, `TypographyPage.php`, `ButtonsPage.php`, `CardsPage.php`, `SidebarPage.php`, `BreadcrumbPage.php`, `SearchPage.php`, `SocialPage.php`, `SeoPage.php` (+ unused `GeneralPage` / `ColorsPage` / `PerformancePage`) |
| **Problem** | These classes are **not** used by `PageFactory` (schema panels use `SchemaDrivenPage`). They retain independent English/`__()` field maps that can diverge from Arabic Schema. |
| **Why** | `ARCHITECTURE.md` §8 single field API; §19 dual systems are debt; maintainability risk for 10-year evolution. |
| **Solution** | Delete unused page classes, or convert to thin deprecated stubs with no field arrays. |
| **Priority** | P1 |

---

### V-05 — High

| | |
|---|---|
| **Severity** | High |
| **Location** | `inc/Pages/DashboardPage.php`, `AppearancePage.php`, `SystemPage.php`, `ImportExportPage.php` |
| **Problem** | Custom `render()` markup bypasses `Components` (hand-built panels/cards/tables/textareas). |
| **Why** | `ARCHITECTURE.md` §7; `DESIGN_SYSTEM.md` §11 — bypassing Components is a contract violation. |
| **Solution** | Rebuild using `Components::panel_*`, `card`, `textarea`, table primitive (when added), empty states. |
| **Priority** | P1 |

---

### V-06 — High

| | |
|---|---|
| **Severity** | High |
| **Location** | `index.php`, `archive.php`, `search.php`, `front-page.php` (enabled empty section shells) |
| **Problem** | Empty experiences are bare paragraphs or empty `<section>` shells without why / next / CTA. |
| **Why** | `DESIGN_SYSTEM.md` §18–§19, §24. |
| **Solution** | Introduce `Components::empty_state` (or frontend equivalent) and migrate empties; give homepage sections content or empty-state CTAs. |
| **Priority** | P1 |

---

### V-07 — High

| | |
|---|---|
| **Severity** | High |
| **Location** | `inc/Admin/UI/Components.php` (`tabs`, `tooltip`); related CSS |
| **Problem** | Tabs expose ARIA roles without full keyboard/ARIA pattern; tooltips are hover CSS only (not keyboard/SR friendly). |
| **Why** | `DESIGN_SYSTEM.md` §10. |
| **Solution** | Complete tabs ARIA/keyboard or remove roles until implemented; make tooltips focusable/accessible or restrict to non-essential decoration. |
| **Priority** | P1 |

---

### V-08 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Pages/SchemaDrivenPage.php`, `inc/Pages/PageFactory.php` |
| **Problem** | Active path calls `Schema::*` directly instead of `FieldRegistry::*`. |
| **Why** | `ARCHITECTURE.md` §8: Field Registry is the single field API over Schema. |
| **Solution** | Switch callers to `FieldRegistry::fields_for()` / `all()`. |
| **Priority** | P2 |

---

### V-09 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Admin/Dashboard.php` (`handle_save`); `inc/Live/SettingsBridge.php`; unused `FieldRegistry::store()` |
| **Problem** | Canonical `store()` exists but form/AJAX reimplement validate→sanitize→save; AJAX may sanitize twice. |
| **Why** | `ARCHITECTURE.md` §8 single orchestrated path. |
| **Solution** | Route saves through `FieldRegistry::store()` after checkbox normalization. |
| **Priority** | P2 |

---

### V-10 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Live/SettingsBridge.php` + `inc/ThemeOptions/Manager.php` |
| **Problem** | AJAX save emits `options_saved` after `Manager::save()`, which already emits it — listeners fire twice. |
| **Why** | `ARCHITECTURE.md` §11 event discipline. |
| **Solution** | Emit once (prefer Manager only; pass `via` in payload if needed). |
| **Priority** | P2 |

---

### V-11 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Admin/Dashboard.php` (`register_settings`) |
| **Problem** | Settings API registration on `KUTBY_OPTION_KEY` creates a dormant write path that sanitizes without Field Registry validation / Manager event story. |
| **Why** | `ARCHITECTURE.md` §8 Manager is sole orchestrator. |
| **Solution** | Remove `register_setting`, or force that path through `FieldRegistry::store()` only. |
| **Priority** | P2 |

---

### V-12 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `footer.php`, `sidebar.php`; thin `inc/TemplateTags/Tags.php` |
| **Problem** | Templates call `Manager` directly; TemplateTags underused (`logo`, `show_section` only). `front-page.php` embeds `WP_Query`. |
| **Why** | `ARCHITECTURE.md` §2/§4 templates should stay presentation-oriented. |
| **Solution** | Expand TemplateTags; move query/section rendering helpers out of templates. |
| **Priority** | P2 |

---

### V-13 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Hooks/FrontendHooks.php` vs `inc/Modules/WidgetsModule.php` |
| **Problem** | Widget areas registered in `FrontendHooks`; `WidgetsModule` is an empty extension stub. |
| **Why** | `ARCHITECTURE.md` §5 feature isolation / Module ownership. |
| **Solution** | Move `register_sidebars()` into `WidgetsModule`. |
| **Priority** | P2 |

---

### V-14 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Admin/Dashboard.php` `enqueue()` |
| **Problem** | `wp-color-picker` and `wp_enqueue_media()` load on every Kutby screen. |
| **Why** | `PERFORMANCE.md` §3 Admin assets — load utilities only when needed. |
| **Solution** | Gate by active panel / schema field types (color, media). |
| **Priority** | P2 |

---

### V-15 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `src/css/main.css` importing page CSS (`home`, `archive`, `single`, `search`) into one frontend entry |
| **Problem** | Page-specific CSS ships globally (files are small stubs today; pattern violates the rule). |
| **Why** | `PERFORMANCE.md` §2 no global CSS for page-specific components. |
| **Solution** | Conditional enqueue / split Vite entries when page CSS becomes non-trivial. |
| **Priority** | P2 |

---

### V-16 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Helpers/Utils.php` `icon()`; Admin nav + footer consumers |
| **Problem** | SVG files are read from disk per call with no request memoization. |
| **Why** | `PERFORMANCE.md` §7 request memoization; repeated hot-path I/O. |
| **Solution** | Static/request cache keyed by icon name. |
| **Priority** | P2 |

---

### V-17 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | `inc/Fields/Field.php` |
| **Problem** | Text/textarea/select/toggle markup duplicated vs `Components::*` (only media delegates). |
| **Why** | `DESIGN_SYSTEM.md` §11 non-duplication; `ARCHITECTURE.md` §7. |
| **Solution** | Route Field renderers through Components. |
| **Priority** | P2 |

---

### V-18 — Medium

| | |
|---|---|
| **Severity** | Medium |
| **Location** | Admin CSS focus/motion (`assets/css/admin/admin.css`, `src/css/admin/admin-components.css`) |
| **Problem** | Incomplete `:focus-visible` use of `--kutby-focus-ring`; animations lack `prefers-reduced-motion`; some physical `left`/`padding-left` / `text-align: left`. |
| **Why** | `DESIGN_SYSTEM.md` §9–§10, §14 logical CSS / reduced motion. |
| **Solution** | Apply focus ring tokens; reduced-motion queries; logical properties. |
| **Priority** | P2 |

---

### V-19 — Low

| | |
|---|---|
| **Severity** | Low |
| **Location** | `inc/Assets/AssetLoader.php`, `inc/Core/Setup.php` |
| **Problem** | Collaborators implement `ModuleInterface` while Module wrappers already own lifecycle. |
| **Why** | `ARCHITECTURE.md` §6 Module vs Service/Loader clarity. |
| **Solution** | Drop interface from loaders/setup; keep methods as plain collaborators. |
| **Priority** | P3 |

---

### V-20 — Low

| | |
|---|---|
| **Severity** | Low |
| **Location** | `inc/Admin/Dashboard.php` |
| **Problem** | Single class owns menu, Settings API, enqueue, render shell, validate/normalize/save. |
| **Why** | `ARCHITECTURE.md` §2/§6 layer focus (not a full god-object yet). |
| **Solution** | Extract `OptionsSaveHandler` (+ optional enqueue helper). |
| **Priority** | P3 |

---

### V-21 — Low

| | |
|---|---|
| **Severity** | Low |
| **Location** | `Tokens::to_css()` + built `tokens.css` both emit full `:root` |
| **Problem** | Duplicate CSS variable emission; Tokens CSS string not request-memoized. |
| **Why** | `PERFORMANCE.md` §6 duplicate CSS; reuse parsed/derived data. |
| **Solution** | Inline overrides only; memoize `to_css()` per request. |
| **Priority** | P3 |

---

### V-22 — Low

| | |
|---|---|
| **Severity** | Low |
| **Location** | `inc/Modules/SearchModule.php` |
| **Problem** | Conditional search JS loads unhashed `assets/js/search.js` outside Vite manifest. |
| **Why** | `PERFORMANCE.md` §6 hashed production assets. |
| **Solution** | Add Vite entry / resolve via Asset Manifest Manager. |
| **Priority** | P3 |

---

### V-23 — Low

| | |
|---|---|
| **Severity** | Low |
| **Location** | `inc/Admin/UI/Components.php` |
| **Problem** | Missing first-class `empty_state` / table / progress primitives called for by Design System sections. |
| **Why** | `DESIGN_SYSTEM.md` §19–§20, §23; encourages one-off page markup. |
| **Solution** | Add primitives + Showcase coverage before more Admin screens. |
| **Priority** | P3 |

---

### Explicitly **not** found (clean)

| Check | Result |
|-------|--------|
| Runtime remote HTTP | None |
| Runtime directory scans | None |
| `get_option(KUTBY_OPTION_KEY)` outside Manager | None |
| Cross-module PHP imports | None |
| Frontend jQuery | None |
| CDN fonts/CSS/JS in production enqueue | None |
| Query-in-loop in Performance/SEO/Setup | None |
| Theme feature logic in `functions.php` | None |

---

# 3. Recommended Improvements (priority order)

### P0 — Before commercial Admin polish

1. Tokenize / replace shipping Admin shell CSS (`admin.css` → tokens).  
2. Complete modal a11y (focus trap, Escape, restore).  

### P1 — Before expanding Admin/frontend UI

3. Fix font preload vs Vite hashed fonts.  
4. Delete or neutralize orphaned Page field-map classes.  
5. Rebuild Dashboard / Appearance / System / ImportExport through Components.  
6. Empty-state system + migrate frontend empties.  
7. Fix tabs/tooltip accessibility (or demote ARIA until real).  

### P2 — Architecture hygiene

8. Route Schema access and saves through Field Registry (`fields_for`, `store`).  
9. Fix double `options_saved` emit; remove dormant Settings API dual write (or unify).  
10. Expand TemplateTags; move front-page query/helpers out of templates.  
11. Move sidebar registration into WidgetsModule.  
12. Gate Admin color picker / media enqueue by panel needs.  
13. Memoize SVG icon reads; plan page-CSS split when stubs grow.  
14. Field renderers → Components; focus-ring + reduced-motion + logical CSS.  

### P3 — Polish / debt burn-down

15. Peel `ModuleInterface` off AssetLoader/Setup; split Dashboard save handler.  
16. Tokens CSS dedupe + request memo.  
17. Search JS via Vite manifest.  
18. Add empty_state / table / progress Components + Showcase.  

---

# 4. Foundation Readiness Score

| Dimension | Score | Rationale |
|-----------|-------|-----------|
| **Architecture** | **82 / 100** | Strong Module Manager / thin Theme / Manager-gated options; debt in orphaned pages, Schema bypass, template/Manager leakage, Widgets split. |
| **Performance** | **80 / 100** | Excellent remote/FS/query hygiene and caching foundations; font preload mismatch, Admin utility over-enqueue, unmemoized icons, global page CSS pattern. |
| **Design System** | **72 / 100** | Solid Components + RTL + Notices; shipping hardcoded Admin shell CSS, Components bypass on custom pages, incomplete dialog/tabs a11y, weak empty states. |
| **Maintainability** | **76 / 100** | Clear contracts and folders; dual Page systems and Dashboard concentration raise long-term cost. |
| **Scalability** | **81 / 100** | Extension via modules/events/registry is sound; dual write/event paths and global asset patterns will hurt as features grow. |
| **Overall** | **78 / 100** | |

### Readiness verdict

**Foundation is stable and frontend-ready for design work**, provided the team treats P0–P1 as the next engineering sprint—not as optional polish.

The contracts (`ARCHITECTURE.md`, `PERFORMANCE.md`, `DESIGN_SYSTEM.md`) are stronger than the worst remaining code debt. Closing P0–P1 would likely move Overall into the mid-80s without architectural redesign.

---

## Score methodology (transparency)

Scores weigh:

- Contract compliance (evidence)  
- Risk to 10-year maintainability  
- User-visible Admin/frontend quality  
- Absence of Critical security/remote/query anti-patterns  

They are **not** marketing scores. They intentionally penalize shipping Design System bypasses and a11y gaps more than unused orphan classes (still High for maintainability).

---

**Audit complete. No code was changed.**
