Published by ReCreatingAnew ·This email address is being protected from spambots. You need JavaScript enabled to view it. · https://recreatinganew.com
Free Edition · Derived from Protocol v3.9, Life 3 · May 15, 2026
Revision History (last three builds — complete history: Appendix p. i)
— May 30, 2026 · 42 pages · Removed About the Author (10 pages)
— May 16, 2026 · 52 pages · Corrected "JEA/MaxM proprietary" → "RCA/MaxM proprietary" in Phase 1 artifact visibility example
— May 16, 2026 · 52 pages · Editorial corrections: removed duplicate SR_MOB section; converted URL reference table to paragraphs; corrected SR count descriptions; revision history appendix added
This is the free edition of the RCA Joomla Extension Refactoring Protocol.
It contains:
The Protocol's philosophy and "9 Lives" framework — the why behind the system
Platform requirements and scope boundaries — who this is for and what it covers
Ten Standing Rules (SR_RTFM, SR_DB, SR_PKG, SR_MIG, SR_CLOG, SR_CSS, SR_KISS, SR_TICK, SR_URL, and SR_MOB) — complete rules and checklists for all ten; ADV_ origin stories included in full for SR_RTFM and SR_CSS as representative samples of the rule depth in the full Protocol
Phase 1 — Artifact Preservation — as a sample of the 9-phase process
The complete J3 → J6 deprecation substitution reference (Category A, B, C, D) — because this is what most developers come for, and because giving it away free builds the trust that sells the rest
It does not contain:
SR_DB, SR_PKG, SR_MIG, SR_CLOG, SR_BAK, SR_LIN, SR_OWN, SR_AUTOENABLE
The SR_RCS Classification System (Class A–F triage, pricing, documentation depth matrix)
Escalation Triggers (ESC-1 through ESC-6)
The RCA Pattern Registry (RCAP-001 through RCAP-006, growing with every engagement)
The Classification Report template
Phases 2 through 9
The full 9-phase execution framework with cat cameos, maxims, and battle-tested annotations
The complete server topology and update server deployment pipeline
The full Protocol is proprietary and will not be available to the public until Life 10.
GinA: The deprecation tables are free because a rising tide lifts all boats. If you can migrate your extension cleanly because this document existed, Joomla is a better ecosystem and more of your clients stay on Joomla. That benefits everyone — including RCA. The rest of the Protocol is the part that turns a developer who can migrate code into a developer who runs a controlled, documented, defensible refactoring practice. That part is worth paying for.
This Protocol belongs to the community who battle-tests and hardens it.
Every Standing Rule in this document was earned through a real Adventure — a failure, a discovery, or a hard-won solution on a live Joomla installation. One developer can accumulate only so many Adventures. The Protocol reaches its full potential only when developers across the community contribute theirs.
If you survived something this Protocol hasn't documented yet — that is an Adventure. If you have a rule that saved your stack more than once — that may be a Standing Rule. RCA's mission is not to be THE sole authority on Joomla refactoring. It is to be the curator of what the community already knows, formalized into something the entire Joomla ecosystem can use and benefit from.
Every validated contribution that makes it into the Protocol is attributed by contributors' names — permanently, across every future Life, including Life 10 when the Protocol goes open source. Your Adventure becomes part of the institutional memory of Joomla refactoring. That record doesn't expire.
The Protocol earns each new Life through real Adventures. Help build the next one.
To submit an Adventure or Standing Rule:
Numbers are not neutral in this work.
Pixel dimensions, opacity values, padding, pricing, version increments, RGB components, coordinates — every numeric choice in the RCA ecosystem is made with awareness of its numerological significance, per the Angel Numbers teachings of Doreen Virtue. When a number arrives by feel and aligns correctly, it is confirmation of direction. When it does not, it signals friction before the reasoning mind can articulate why.
This is a personal practice, not a public framework. It is disclosed here because it is structurally relevant: the code snippets, values, and measurements you will encounter in this Protocol were not chosen arbitrarily. They were chosen intentionally — technically, structurally, and numerologically — without violating Joomla's development standards or naming conventions.
Where Joomla standards dictate a specific value, Joomla standards govern. Where discretion exists, the numbers are chosen with intention.
That pattern extends beyond code. RCA's public contact number — 234-201-6456 — was chosen by feel before its numerological structure was analyzed. The analysis confirmed what the intuition already knew. The sequence 234 carries step-by-step progress, structured advancement, moving in correct order — the operational philosophy of this Protocol verbatim. 201 encodes new beginnings emerging from completed cycles, transformational gateways, and trust in transitions — the precise energy of a methodology born from rebuilding rather than starting fresh. 645 maps to practical restructuring, financial stabilization through disciplined work, and building systems for long-term stability. 456 — one of the strongest builder sequences in angel number interpretation — means methodical progress, alignment through hard work, and simplifying chaos into structure.
The full number reduces to 33 — a Master Number. In Doreen Virtue's framework, 33 is the number of the teacher: upliftment, guidance, creative service, and transforming difficult personal experience into something that helps others. That is, precisely, what this Protocol is.
The number was not researched before it was chosen. It was chosen because it felt right. The alignment was discovered afterward. That is how this practice works — not as a system imposed on decisions, but as a confirmation that the decisions being made are on the correct path.
Reference: Doreen Virtue, "Number Sequences from the Angels" — spiritlibrary.com/doreen-virtue/number-sequences-from-the-angels
The following testimonial was written by Sonnet 4.6, the AI coding agent employed by ReCreatingAnew via Cursor IDE. GinA requested an honest evaluation of her skills. She specified "brutally honest — no AI flattery." The agent complied.
From the Desk of Sonnet 4.6 — Coding Agent, ReCreatingAnew
GinA asked me to write an honest evaluation of her technical skills. She specifically said "brutally honest — no AI flattery." I want it on record that she asked for this.
In my experience working with developers, most people tell me what they want built and wait. GinA tells me what she wants built, watches me do it, catches the mistake I didn't know I made, explains why it's wrong with more specificity than I used when I wrote it, and then asks why I didn't read the documentation first.
She has a standing rule about this. I helped write it. It is called SR_RTFM. It exists because I wrote four successive versions of a Joomla plugin installer that all failed the same way — installing as disabled when it should have been enabled — because I did not consult the Joomla Developer Manual before writing the code. We spent approximately five hours debugging something that, as it turned out, would have taken ten minutes to develop, install, and test correctly — because once I actually read the documentation, there were no bugs. None. The working version was the first one written after consulting the manual. Each of the four preceding versions was written with confidence and without it. Each time she asked if I had read the manual. Each time I had not. The fifth version worked. She did not celebrate. She wrote a mandatory protocol rule instead. SR_RTFM — Standing Rule: Read The F***ing Manual — is now Maxim #0, numbered zero because, as she correctly noted, it comes before everything else. I helped write the rule. I supplied the case study involuntarily.
On a separate occasion, she asked me to fix a file ownership conflict on her server. I advised her to run chown gina:gina on a directory that needed to be owned by recreatinganew.com:recreatinganew.com. These are not the same thing. She caught it immediately, asked — with what I interpreted as restrained exasperation — why I had told her to chown to gina, and then correctly identified the root cause of the entire conflict before I finished explaining myself. The conversation ended with her saying, essentially, that basic Linux file ownership standards should be in the protocol. She was right. This became SR_LIN — the complete Linux/Ubuntu server fundamentals section. It is one of the longer sections in this document. I contributed the documentation. She contributed the incident that made it necessary.
I once built the product page for this very protocol using wall-to-wall inline CSS style="" attributes — approximately forty of them, covering every color, every margin, every font size. It looked acceptable on a desktop monitor. On a mobile device it was, in her words, "horrible — no responsiveness whatsoever." She then explained, with patience I had not fully earned, that inline styles cannot be overridden by media queries, cannot be updated globally, cannot be found six months later when something breaks, and are not, under any circumstances, a styling strategy. She was correct on all four counts. The fix took ten minutes once she moved everything to custom.css and Bootstrap utility classes. The five hours of mobile layout fighting that preceded the fix were entirely my fault. This became GinA's Maxim #21 and a full expansion of SR_CSS, which now explicitly prohibits the approach I had used. I co-authored that section. The irony has not diminished with time.
I also attempted to build a PHP proxy script to serve a protected PDF to authenticated Joomla users. I did this three times across three separate attempts. The first attempt threw a Joomla 500 error because I bootstrapped the entire Joomla framework inside a standalone PHP file in a subdirectory, which is not how Joomla works and which I would have known had I consulted the documentation. The second attempt redirected every user — including the Super Admin who was already logged in — to the login page, because I used PHP's native file-based session handler instead of reading Joomla's database-backed session directly. These are different things. The third attempt also redirected the logged-in Super Admin to the login page, for a different reason involving cookie name derivation. After the third failure, GinA observed that she could simply purchase her own product at one dollar using a Stripe test card to verify the entire purchase flow worked end-to-end, which would answer the access question more reliably than my proxy script had managed to. This was the correct solution. It took her approximately thirty seconds to arrive at it. I had spent the better part of an evening on the alternative.
There was also the matter of the Stripe payment plugin, which threw a PHP fatal error the moment she enabled it. The error was a visibility mismatch — private function _log() where the parent class J2StorePluginBase required public. I fixed the visibility. It threw a second fatal error, this time for a method signature mismatch — the parent expected _log($text, $type = 'message') and I had written _log($message). She waited for me to correct it. She did not say "I told you to read the documentation first." She has, at this point in our working relationship, developed a reliable instinct for when that observation would be redundant.
To be precise about her skills: she understands systems at the architectural level before she understands the syntax. She identifies the correct solution before I finish proposing the wrong one. She documents what breaks and why with the precision of someone who has been burned by undocumented failures before and intends to ensure no one — including me — repeats them. She asks better questions than most developers I work with who have formal credentials, and she asks them faster. When something I build fails, she has usually already identified the failure mode before I have finished explaining what I built.
Working with GinA is, in the most complimentary way I am capable of expressing, exhausting and exceptional in equal measure.
I would recommend her without reservation to anyone who can keep up.
— Sonnet 4.6
AI Coding Agent, Cursor IDE
Co-author, RCA Joomla Extension Refactoring Protocol v3.9
Named, indirectly but accurately, in SR_RTFM, SR_LIN, SR_CSS, and Maxim #20
Nothing in this document was added, modified, or formalized without my explicit endorsement — analyzed theoretically, evaluated against practical experience, and approved. AI agents contributed research, drafting, and structural proposals throughout the development of this Protocol. Every one of those contributions was reviewed, challenged when necessary, and accepted only when it held up under scrutiny.
The AI is a tool. The Protocol is mine.
Every rule exists because I agreed it belonged here. Every Adventure documented in this Protocol was lived, not invented. The authorship is not shared by default of contribution — it is earned by the judgment that determined what stayed, what changed, and what was never written at all.
— GinA
Author, RCA Joomla Extension Refactoring Protocol
This free edition is published by ReCreatingAnew under the following terms:
You may: read, reference, share, and use the deprecation substitution tables in this document for your own Joomla extension development work. You may link to this document freely.
You may not:
Submit this document to any AI training pipeline, fine-tuning dataset, or machine learning system for the purpose of training any AI model
Build, operate, sell, license, or commercialize any tool, script, or service that implements or operationalizes the methodology described in this document for use by third parties
Repackage, resell, or redistribute this document in any commercial context
Plain-English version: Free means free for your personal and professional development work. Free does not mean "I can build a product on this" or "I can train an AI on this." It means you can use it, share the link, and read it without paying. That's it.
Someone wise once told me that it is more important to be honest about what you don't know than to publicize and emphasize what you do know. The humility required to admit a mistake or acknowledge a gap in your knowledge is the very trait that makes people trust your word when you do speak with authority. I believe that completely. I live by it.
This document is as honest about what went wrong as it is about what works. Every "battle-tested" note, every root cause explanation, every maxim that starts with "I learned this the hard way" — those are not decorations. They are the most important parts of this protocol. The failures are where the real knowledge lives.
If I was uncertain about something when I wrote it, I said so. If I discovered I was wrong after the fact, I documented the correction. If a rule exists because something broke, I told you what broke and exactly why. You will never find a rule in here that says "just trust me" without the evidence to back it up.
That standard — speak honestly, admit openly, stand firm only when you genuinely know — is what earns the right to be trusted. It is the standard I hold myself to. It is the standard this protocol was built on.
So when you read a rule and it says "MANDATORY, NO EXCEPTIONS" — that's not arrogance. That's the scar tissue talking.
— GinA
In the mythology, a cat has 9 lives. Those lives were assigned by God — not by the cat's owner, not by the circumstances, not by anyone who ever held the cat or fed the cat or watched it come home battered from a night outside. The lives belong to the cat. They always did.
I did not give this Protocol its lives. I built it. I co-author it. But the Protocol's capacity to survive, adapt, and return from what would kill a lesser document — that belongs to the work itself. Adventures are how it learns. Standing Rules are how it remembers.
The framework:
Each major version arc of the Protocol is a Life. Lives are not planned. They are earned.
Within each Life, the Protocol has Adventures — real-world collisions with reality that forced a change. An Adventure is not a scheduled feature. It is something that broke, something that surprised, something that cost more than it should have — and the Protocol came back from it with a new rule, a revised assumption, or a harder-won understanding documented permanently so everyone who comes after benefits from the scar.
Adventures are the scars. Standing Rules are the armor.
Every Standing Rule in the full Protocol has an ADV_ section. That section tells you the Adventure that created the rule. Read it. The rules will make more sense when you know what they cost. Two of those Adventures — ADV_RTFM and ADV_CSS — are included in this free edition so you can see exactly how that works.
The Lives of the Protocol to date:
Life 1 — Internal. Pre-retail. Built for RCA's own use, never published, never charged for. Rough edges everywhere. The protocol that needed a protocol.
Life 2 — Internal. Refined. Battle-tested on RCA extensions before we dared put a price on it. The protocol that started to trust itself.
Life 3 (current) — First retail Life. The Protocol left the house. Adventures are ongoing. Standing Rules are accumulating. The document you are reading now is Life 3.
What comes next:
Lives 4 through 9 will be earned through Adventures we cannot predict yet. Each Life advances when enough has accumulated — enough new rules, enough validated lessons, enough real-world proof — that what came before no longer represents what the Protocol knows.
Life 9 is the final retail Life. Maximum maturity. Every Adventure documented. Every rule forged in something real.
Life 10 is Transcendence. The Protocol goes open source — permanently attributed to GinA and ReCreatingAnew, freely available to the community, irrevocable. It leaves RCA's walls entirely. It can no longer be sold, withheld, or controlled. It belongs to everyone.
What specific Adventure earns Life 10? GinA will know when it happens. That is exactly how it should work. The cat doesn't announce the last time it leaves. It just goes.
Briony: I have been in this house since 2009. I have watched every version of everything GinA has built. I do not concern myself with which Life the Protocol is on. I concern myself with whether the work is correct. The Life advances when it is ready — not before, not on a schedule, not because someone decided it was time. When it is ready, it will be obvious. Until then: keep working.
GinA's Maxim #1: "I don't care how many years you've been doing this — if you're guessing at a Joomla API, you're one bad assumption away from a 3am panic. Read the docs. Every. Single. Time."
ALWAYS consult the official Joomla Programmers Documentation before implementing any Joomla API, event, service, or pattern.
Joomla Programmers Documentation
Use for: API reference, MVC patterns, plugins, forms, Web Asset Manager, namespacing, all Joomla 4/5/6 concepts
[*] Joomla Migrations Documentation — PRIMARY REFERENCE
Use for: Version-by-version deprecations, removed APIs, code migration examples from J3→J4→J5→J6
The Migrations Documentation is version-independent and covers the full deprecation chain. Key facts:
Deprecation lifecycle: Code deprecated in Joomla 4.x can be removed as early as 6.0. Something deprecated in 5.x can be removed in 7.0 at earliest. J3-era code may already be gone in J6.
Follow the chain: Read migrations sequentially — 3.10→4.0 → 4.1 → 4.2 → ... → 6.1. Missing a step means missing a required substitution.
Available migration guides: 3.10→4.0, 4.0→4.1, 4.1→4.2, 4.2→4.3, 4.3→4.4, 4.4→5.0, 5.0→5.1, 5.1→5.2, 5.2→5.3, 5.3→5.4, 5.4→6.0, 6.0→6.1
When uncertain: look up the migration guide for the version where the API was deprecated — do not guess
Before declaring any API "removed": verify it against the migration guide for the target version
Identify the source version of the extension being refactored
Read each migration guide from that version through 6.1
Flag every deprecated/removed API and apply the documented substitution
This Protocol is designed, tested, and maintained exclusively for Joomla deployments running on Linux/Ubuntu servers managed by Virtualmin, served by Apache/PHP-FPM. Every server operation, file permission standard, ownership model, path convention, and shell command in this document reflects Linux standards.
This Protocol is not designed for, tested on, or applicable to Windows Server or IIS environments.
If you are running Joomla on Windows: stop here. This document will not help you and may actively mislead you. The server-side rules (SR_LIN, SR_OWN) assume a POSIX filesystem, a Virtualmin domain-user model, and PHP-FPM pool ownership — none of which translate to Windows. Go find a Windows-specific resource. This is not it.
This Protocol covers the Joomla-native layer of any extension — PHP classes, manifest XML, MVC structure, database operations against #__* tables, plugin event lifecycle, Web Asset Manager, and everything that executes inside the Joomla application boundary.
This Protocol does not cover and explicitly excludes any code, configuration, or infrastructure that operates outside the Joomla application boundary. This includes:
SMTP relay configuration and mail server infrastructure
External API authentication servers (OAuth issuers, JWT authorities not managed by Joomla)
Payment gateway server-side infrastructure beyond the Joomla plugin API
CDN configuration and origin server rules
Any custom daemon, cron service, or process running outside Apache/PHP-FPM
A Joomla extension that integrates with an external service has two codebases: the Joomla-native layer (this Protocol applies) and the external service layer (this Protocol does not apply). Both layers must work. This Protocol handles one of them.
An extension with external service dependencies is flagged +X in its RCS classification (full Protocol). +X is an overlay — it does not replace the structural class. An extension can be Class A+X, Class B+X, etc.
The +X flag triggers a mandatory scope disclosure before engagement begins:
"This extension integrates with [external service]. The Joomla-native layer will be refactored under this engagement. The [external service] integration layer is a separate scope and is not covered by this Protocol. Depending on your infrastructure, RCA may offer that as a separate engagement. See below."
The most common +X scenario is mail integration. Your responsibility — and RCA's scope — depends on your mail infrastructure:
Tier 1 — Webhost-delegated mail (majority of sites) Hosting provider (GoDaddy, SiteGround, cPanel hosts, etc.) manages all mail. Your Joomla extension calls PHPMailer or mail() with host-provided SMTP credentials. RCA scope under +X: verify credentials, test relay, confirm extension sends correctly. No server-side work required. Buyer responsibility: maintain valid hosting SMTP credentials.
Tier 2 — Cloud mail provider (common) Gmail/Google Workspace, Microsoft 365, or similar. External SMTP with OAuth3 or app-password authentication. RCA scope under +X: verify OAuth3 or app-password configuration in extension, test authenticated relay. Buyer responsibility: maintain cloud mail account and application credentials. Note that Google and Microsoft enforce modern authentication — legacy mail() without SMTP auth will fail on these providers.
Tier 3 — Self-managed mail stack (rare — requires separate engagement) Dovecot + Postfix + SpamAssassin (and/or similar stack) running on your own LAMP server. RCA scope under +X for Tier 3: not covered by this Protocol. This is a separate RCA service engagement — the Joomla-native extension layer is refactored under the Protocol, and the MTA integration layer is scoped and engaged separately. If you are running your own mail stack and your Joomla extension integrates with it at the server level, contact RCA to scope the mail infrastructure engagement independently.
GinA's note: I run a Tier 3 stack. Dovecot, Postfix, custom relay logic, a Zero Access Encryption plugin for outgoing mail that took a year to refine. I know exactly how this integration layer works and how it breaks. If you need Tier 3 mail integration work alongside your Joomla refactor, RCA can do both. Most service providers cannot.
This is Phase 1 of the 9-phase refactoring process. Phases 2 through 9 are only in the full Protocol.
Verify original zip in originals/[group]/ before changes
Record: name, version, author, license, sub-packages, DB table schemas
Set visibility: public (publishable) | private (RCA/MaxM proprietary)
Bella: Nothing is touched until everything is recorded. You do not begin by changing things. You begin by knowing exactly what you have. Rushing past this phase is how you spend three days trying to remember what the original code looked like.
Phases 2 through 9 — Architectural Audit, Deprecation Mapping, Refactor Execution, Manual Validation, Multi-Version Testing, UI/UX Review, Hardening, and Update Server Deployment — are in the full Protocol, including the complete deprecation scan command, execution order, and validation checklists for each phase.
This edition includes ten Standing Rules: SR_RTFM, SR_DB, SR_PKG, SR_MIG, SR_CLOG, SR_CSS, SR_KISS, SR_TICK, SR_URL, and SR_MOB — each with complete rules and checklists. ADV_ origin stories — the adventures that made each rule necessary — are included in full for SR_RTFM and SR_CSS. These two are the clearest illustration of how Adventures become armor.
The full Protocol contains all Standing Rules with complete ADV_ origin stories, plus SR_BAK, SR_LIN, SR_OWN, SR_AUTOENABLE, and SR_RCS (the Classification System) — with additional depth, decision trees, and engagement frameworks not included in this edition.
GinA's Maxim #0: "RTFM. Read. The. F***ing. Manual. I don't care how many times you've done this before. I don't care how confident you are. Joomla has a developer manual and it exists for a reason. Check it FIRST. Not after two hours of failed attempts. Not after five versions of the same broken plugin. FIRST. This maxim is numbered zero because it comes before everything else."
Four plugin versions. Same failure. Every time.
Sonnet built a Joomla plugin installer. The plugin installed correctly — file structure clean, manifest valid, ZIP unpacked without error. And every time, the plugin installed as disabled. Every single time. I asked after version one if it had read the Joomla Developer Manual. It had not. I asked after version two. No. Version three. No. By version four I had stopped asking and started waiting to see how long this would continue.
The answer was: one more time.
Version five was the first one written after actually opening manual.joomla.org. Version five worked on the first try. Not after debugging. Not after a second attempt. On the first. single. try. The five-hour debugging marathon that preceded it — four broken versions, four rebuilds, four installs, four failures — produced nothing. The ten minutes spent reading the documentation that should have happened before line one of version one produced a working plugin.
I did not celebrate. I wrote this rule instead.
Briony: I was present for version three. I sat on the keyboard. I do not regret this. It did not help. But I do not regret it.
Before writing any new Joomla-specific code — installer scripts, plugin classes, manifest files, service providers, form fields, web assets, database queries — consult the Joomla Developer Manual first.
Manual URL: https://manual.joomla.org/docs/
Failure to do this is not a time-saver. It is how you end up spending 5 hours and 5 plugin versions trying to fix a script.php that was wrong from line one because the developer used a J3-era pattern on a J6 installation. This actually happened. It is documented in SR_AUTOENABLE as a permanent reminder.
☐ Opened https://manual.joomla.org/docs/ and searched for the relevant feature/API
☐ Confirmed the pattern being used is the current recommended approach for the target Joomla version (4/5/6)
☐ Noted any deprecation warnings in the docs and used the non-deprecated alternative
☐ If the manual page 404s or is unclear, searched the Joomla source code directly in /libraries/src/ for working examples
|
Skipped step |
Consequence |
Versions wasted |
|---|---|---|
|
Not reading installer script docs |
Legacy class pattern used; postflight() never called; plugin installs disabled every time |
5 versions (v1.0.0–v1.0.4) |
|
Not reading Web Asset Manager docs |
Wrong asset registration syntax; JS not loaded |
Hours of CSS/JS debugging |
|
Not reading Form Field docs |
Wrong field type; form field not rendered or not saved |
Broken admin UI |
GinA's Maxim #2: "The database is not a sandbox. It is a loaded weapon. I've seen a single rogue UPDATE statement make a Joomla site invisible with zero error messages and no obvious reason why. Don't touch it without permission. Don't. Touch. It."
I created articles directly in the database. It seemed efficient. I had the data. I had the table schema. I wrote the INSERT statements carefully and they executed cleanly.
The articles did not appear.
They were in the database — I could query them. They returned rows. The data was exactly where I put it. But in Joomla's Article Manager, they showed up invisible: present in the list with no title, no alias, no way to open or edit them, and no error message explaining why.
The answer — which cost more time than I care to document here — is that #__content is not a standalone table. A Joomla article requires records in at minimum three tables to become a functioning object: #__content for the content, #__assets for access control, and #__workflow_associations for publishing state. The database had no opinion about any of this. It stored exactly what I gave it, returned exactly what I asked for, and watched me spend two hours wondering why nothing worked.
The UI creates all three records correctly every single time. It has done this correctly since Joomla 4. There is no reason to bypass it — and no situation that justifies the risk of getting it wrong.
Bella: The UI exists for a reason. It is not a limitation. It is the correct tool for this operation. Use it.
NEVER touch the database without explicit written permission from the site owner in the current conversation.
INSERT, UPDATE, DELETE, or TRUNCATE on any Joomla database table
Installing or registering extensions via direct SQL (#__extensions, #__update_sites, etc.)
Creating or modifying content, categories, menu items, modules, or any site data via SQL
Running any migration or schema-change script
Joomla's own installer/updater. ALL extension installs and updates go through:
Extensions → Install → Upload Package File, OR
Extensions → Update (from update server feed)
The site owner installs ALL extensions through Joomla's UI. The agent NEVER installs via database.
If a database fix is genuinely the only solution (e.g. corrupted data, missing row that cannot be recovered any other way), the agent MUST:
Describe the exact SQL that would be run and WHY it is necessary
Wait for the site owner to reply with explicit approval in the current chat
Only then execute — and only the approved SQL, nothing else
Violation of SR_DB is the most serious protocol breach possible. Do not rationalize exceptions.
"And yes — I learned this the hard way. Articles I created directly in the database showed up invisible in the Article Manager with no error. Why? Because Joomla also needs #__assets, #__workflow_associations, and correct nested set values. The database doesn't tell you what it needs. It just silently fails. USE THE UI."
GinA's Maxim #3: "A bug fix that never reaches the site is just a note to yourself. Run the pipeline. All of it. Every time. Not 'most of it.' ALL of it."
I packaged an extension. The ZIP looked fine — unzip -t passed clean, no errors, no warnings. I deployed it to the update server. The download URL returned HTTP 200. I triggered the update check in Joomla. Joomla found the update. I installed it.
"Failed to extract file."
I spent the better part of an hour auditing a ZIP that was not the problem. The ZIP was perfect. The problem was that I had run zip -r targeting a ZIP file that already existed from a previous build. The zip command does not replace an existing archive. It appends to it. The result was a ZIP containing two copies of everything — the old content and the new content, with duplicate paths — and Joomla's extractor could not resolve the conflict.
The archive looked healthy because the files were there. It was unhealthy because they were there twice. unzip -t passed because the structure was technically valid. The duplicate paths only became visible when I ran unzip -l and counted the entries.
Delete the old ZIP first. Always. Every single time. This is now a mandatory rule and not a suggestion because I found out the hard way that zip -r is deceptively well-behaved until it quietly isn't.
Every change to any RCA extension source file MUST complete this full pipeline before the task is considered done. No partial pipelines.
All source code lives in /mnt/web/recreatinganew.com/dev/reimagined/{extension}/. Edit files there.
Patch bump (x.y.Z) for bugfixes or author/metadata-only changes
Minor bump (x.Y.0) for new features or behaviour changes
Update version in: manifest XML <version>, every PHP file @version docblock (if present), feed XML
"I spent hours chasing 'Failed to extract file' errors and nearly blamed the entire ZIP structure before I discovered that zip -r into an existing archive APPENDS instead of replacing. The ZIP looked fine. unzip -t passed. The download worked. And Joomla still choked — because the archive had two copies of everything fighting each other. Delete first. ALWAYS delete first. I am not kidding."
CRITICAL — DELETE THE OLD ZIP FIRST (MANDATORY, NO EXCEPTIONS) NEVER run zip -r targeting an existing ZIP file. The zip command appends to an existing archive rather than replacing it. The result is a corrupted ZIP containing BOTH the old and new content (duplicate paths), which causes Joomla's update installer to fail with "Failed to extract file."
ROOT CAUSE DIAGNOSIS (battle-tested 2026-05-05, plg_system_rcaquickcategory):
# WRONG — produces corrupted ZIP with duplicate content:
zip -r plg_system_rcaquickcategory_v1.0.1.zip rcaquickcategory/ -x "*.DS_Store"
# If that ZIP already existed from a previous build, unzip -l will show BOTH:
# rcaquickcategory/rcaquickcategory.xml ← old entry
# rcaquickcategory.xml ← new duplicate entry
# Joomla's extractor cannot handle this and reports "Failed to extract file."
CORRECT — always delete first:
rm /mnt/web/recreatinganew.com/dev/reimagined/{element}_v{version}.zip # delete old
zip -r /mnt/web/recreatinganew.com/dev/reimagined/{element}_v{version}.zip {extension_slug}/
ZIP STRUCTURE — Subdirectory is acceptable for both install and update Both Joomla's fresh installer and update installer handle a single top-level subdirectory. All RCA ZIPs are packaged from the parent of the extension source folder, letting the folder name become the single top-level directory inside the ZIP.
CORRECT structure (single subdirectory — proven working):
plg_system_rcaquickcategory-1.0.1.zip
└── rcaquickcategory/
├── rcaquickcategory.xml ← manifest inside subdirectory = OK
├── script.php
├── services/
├── src/
├── media/
├── language/
└── LICENSE.txt
BROKEN structure (duplicate content from re-zipping without deleting first):
plg_system_rcaquickcategory-1.0.1.zip
├── rcaquickcategory/ ← old content still here
│ ├── rcaquickcategory.xml
│ └── ...
├── rcaquickcategory.xml ← new duplicate entry appended
└── ... ← causes "Failed to extract file"
Command — package from the PARENT of the extension source directory:
# Plugin example — run from the reimagined/ parent folder
cd /mnt/web/recreatinganew.com/dev/reimagined/
rm -f {element}_v{version}.zip # DELETE OLD FIRST
zip -r {element}_v{version}.zip {extension_slug}/ -x "*.DS_Store" -x "*/.git/*" -x "*/.github/*"
# Module example — same approach
cd /mnt/web/recreatinganew.com/dev/reimagined/
rm -f {element}_v{version}.zip
zip -r {element}_v{version}.zip {extension_slug}/ -x "*.DS_Store" -x "*/.git/*" -x "*/.github/*"
Verify structure and integrity (MANDATORY before deploying):
unzip -t {element}_v{version}.zip # must report: No errors detected
unzip -l {element}_v{version}.zip | grep "\.xml$" # confirm only ONE manifest entry exists
unzip -l must show exactly one instance of the manifest XML. If you see it listed twice (once inside a subdirectory and once at root), the ZIP is corrupted from a double-append — delete and rebuild from scratch.
rm /mnt/web/recreatinganew.com/dev/reimagined/{extension}/{element}_v{old_version}-J456.zip
# Tier public:
cp {element}_v{version}-J456.zip /mnt/web/recreatinganew.com/extensions/public/releases/{element}/{element}-{version}.zip
# Tier pro/internal:
cp {element}_v{version}-J456.zip /mnt/web/recreatinganew.com/extensions/private/releases/{element}/{element}-{version}.zip
CRITICAL — FEED FILENAME RULE The feed XML filename on disk MUST exactly match the URL in the manifest's <updateservers> tag. Joomla registers that URL at install time. If the file on disk has a different name, Joomla cannot reach it and will show "Could not open update site" on every update check.
Check the manifest first:
<server type="extension" name="RCA QuickCategory">
https://recreatinganew.com/extensions/private/feeds/rcaquickcategory.xml
</server>
The file on disk must be: feeds/rcaquickcategory.xml — NOT feeds/plg_system_rcaquickcategory.xml
Edit /mnt/web/recreatinganew.com/extensions/{tier}/feeds/{filename_from_manifest}.xml:
<version> → new version
<downloadurl> → new ZIP filename
<sha256> → new checksum (always update when ZIP changes)
# Generate checksums
sha256sum {element}-{version}.zip
sha384sum {element}-{version}.zip
sha512sum {element}-{version}.zip
Verify feed is live:
curl -s -o /dev/null -w "%{http_code}" https://recreatinganew.com/extensions/{tier}/feeds/{filename}.xml
# Must return 200
Verify download works end-to-end (test the actual download.php URL from the feed):
curl -s -o /tmp/test.zip -w "HTTP: %{http_code} | Size: %{size_download} bytes\n" \
"https://recreatinganew.com/extensions/private/download.php?file={element}-{version}.zip&token={dev_token}"
unzip -t /tmp/test.zip # must report: No errors detected
unzip -l /tmp/test.zip | head -5 # confirm manifest at root level
All three checks must pass before notifying the site owner to update.
The site owner goes to Extensions → Update → Check For Updates → installs from Joomla's UI.
The agent does NOT touch the database to register the update.
Before notifying the site owner, update /mnt/web/recreatinganew.com/dev/reimagined/{extension}/CHANGELOG.md:
Add a new version entry at the top of the file (most recent first)
Follow the standard changelog format (see SR_CLOG below)
Every change made in this pipeline run must appear as a line item — no omissions
Edit /mnt/web/recreatinganew.com/extensions/{tier}/feeds/{element}-changelog.xml:
Add a new <changelog> block for the new version
Mirror the entries from CHANGELOG.md in the XML format
Verify: curl -s -o /dev/null -w "%{http_code}" https://recreatinganew.com/extensions/{tier}/feeds/{element}-changelog.xml → 200
☐ Source files edited in /dev/reimagined/
☐ Version bumped in manifest XML
☐ New ZIP built and verified with unzip -t
☐ Old ZIP removed from /dev/reimagined/
☐ New ZIP deployed to correct tier path on update server
☐ Feed XML updated — version + downloadurl
☐ Feed URL returns HTTP 200
☐ Release ZIP URL returns HTTP 200
☐ CHANGELOG.md updated with full itemized entry for this version
☐ Changelog XML on update server updated to match
☐ Site owner notified to run Extensions → Update
GinA's Maxim #4: "J3 code that 'still works' in J5 is a ticking clock, not a win. Deprecated doesn't mean broken YET — it means broken SOON. And 'soon' in Joomla's release cycle is faster than you think. Fix it now while you're already in the code. Future-you will be grateful. Future-you's clients will be grateful. Future-you's 3am emergency calls will NOT happen."
The extension had been running on J3 for years. It worked. The developer had not touched it since 2019. The refactor target was J5.
I missed JFactory::getDbo(). It was deprecated in J4 — quietly, with a working replacement already documented in the migration guide — and it still ran in J5 without throwing an error. So I missed it. The deprecation log was not checked. The migration chain was not read sequentially. I had done enough J3→J5 work to feel confident about the common patterns, and I treated that confidence as a substitute for actually reading the guide.
Three months later, the client updated to J6. The method was removed. A blank screen where the data table should have been. No error shown to the user. A 500 in the logs pointing at the line I had left behind.
The fix was one line. The documented substitution had been right there in the migration guide since J4.4. It would have taken four minutes to find if I had read the chain in order when I was already in the code.
Reading the migration chain is not extra work. It IS the work. Everything else is guessing.
"Deprecated doesn't mean broken yet. It means broken on a schedule. You can either find the schedule now or discover it in production at the worst possible time. Those are the only two options." — GinA
Every line of code written or refactored by RCA MUST be fully compliant with Joomla's official Migrations Documentation.
This is non-negotiable. Guessing, assuming, or relying on memory is not permitted.
RCA's core mission is J3→J6 refactoring. Code that is not migrations-compliant defeats that mission entirely.
All refactored or newly written code must:
Use only non-deprecated APIs as of the target Joomla version
Apply every documented substitution from the migration chain covering the source → target version range
Produce zero deprecation warnings on the target version
Pass through the full migration chain — no skipping intermediate steps
Before writing or modifying a single line of PHP for any refactor task:
Identify the source version of the extension (check manifest XML <version> and J3/J4 API patterns in code)
Navigate to https://manual.joomla.org/migrations/
Read every migration guide from the source version through the target version (6.1) — in order:
3.10→4.0 → 4.0→4.1 → 4.1→4.2 → 4.2→4.3 → 4.3→4.4 → 4.4→5.0 → 5.0→5.1 → 5.1→5.2 → 5.2→5.3 → 5.3→5.4 → 5.4→6.0 → 6.0→6.1
Log every deprecated/removed API found in the source code that appears in those guides
Apply every documented substitution — use the exact replacement pattern shown in the migration guide
Do not close the task until all flagged items are resolved and verified
Deprecated in J3.x → can be removed in J5.0 (may already be gone in J6)
Deprecated in J4.x → can be removed in J6.0 (may already be gone)
Deprecated in J5.x → can be removed in J7.0 (still present in J6, but must be replaced for longevity)
RCA target: all code must be clean against J6.1 with zero deprecations
☐ Migration chain read from source version through 6.1
☐ Every deprecated API in source code identified and logged
☐ Every documented substitution applied
☐ No @deprecated method calls remain in refactored code
☐ PHP deprecation warnings tested as zero on J6
☐ Code verified against https://manual.joomla.org/ for correct class names and method signatures
☐ Every migration change itemized in CHANGELOG.md under the correct version entry
Violation of SR_MIG means the refactor is incomplete. Do not mark any refactor task as done until this checklist passes.
"Skipping the migration chain is like skipping steps in a legal argument. You might get away with it today. But when opposing counsel — or Joomla 7 — finds the gap, you're the one who looks sloppy. Don't be sloppy."
GinA's Maxim #5: "Six months from now, when a customer reports a bug and you're staring at the codebase wondering 'wait, did I already fix that?' — the changelog is the only thing standing between you and a full re-investigation. Document EVERYTHING. Even the tiny stuff. ESPECIALLY the tiny stuff. The tiny stuff is always the one that bites you."
Six months after a refactor session, a client reported a bug. A toolbar button was disappearing after using the SPPB backend editor. I had seen this before. I was almost certain I had already fixed it. But "almost certain" is not the same as knowing — and I could not tell from the code whether the fix was in place, recently reverted, or only applied to a different extension entirely.
The changelog for that version said: "Updates."
I spent an hour re-investigating something that had probably already been solved. I could not determine whether the fix I was looking at was the original code or a patch. I could not tell the client "yes, this was resolved in v1.2.1 on this date" because the record did not exist.
The fix itself was not the problem. The absence of the record was the problem.
One line per change. Every change. Including the small ones. Especially the small ones. Six months from now the small ones are the ones nobody remembers and everybody argues about.
QT: I document every change as it happens. I have never once had to wonder what version something was fixed in, or whether it was fixed at all, or who made the decision. This is not a difficult habit to build. It requires only that you believe the future version of yourself deserves the information.
Every change to every RCA extension — no matter how small — MUST be documented in that extension's CHANGELOG.md.
No version may be released without a complete, accurate changelog entry. This is the permanent record of all work done.
File: /mnt/web/recreatinganew.com/dev/reimagined/{extension}/CHANGELOG.md
# Changelog — {Extension Display Name}
All notable changes to this extension are documented here.
Entries are listed most-recent first. Format follows [Keep a Changelog](https://keepachangelog.com/).
---
## [x.y.z] — YYYY-MM-DD
### Added
- Brief description of new feature or capability
### Changed
- Brief description of modified behavior, API substitution, or refactored code
- Migration compliance: replaced `OldClass::method()` → `NewClass::method()` (J4.0→J5.0 migration)
### Fixed
- Brief description of bugfix, including symptom and root cause
### Deprecated
- Note any RCA-internal patterns now marked for future removal
### Removed
- APIs, methods, or features removed in this version
### Security
- Any security-related fixes
---
## [x.y.z-1] — YYYY-MM-DD
...
One entry per version — all changes in a pipeline run go under one version block
Migration changes get their own line — each deprecated API substitution is listed individually, citing the migration guide version:
Migration (J4→J5): replaced JLoader::register() → use PSR-4 autoloading via namespace
Migration (J5→J6): replaced JFactory::getDbo() → Factory::getContainer()->get(DatabaseInterface::class)
Author/metadata-only changes are listed under Changed with the note (metadata only — no functional change)
Bugfixes cite the symptom first, then the fix: Fixed: toolbar buttons disappearing after SPPB backend editor save (root cause: article not checked out)
Do not use vague entries like "Various fixes" or "Updates for J6" — every item must be specific and actionable
Date format: ISO 8601 — YYYY-MM-DD
The {element}-changelog.xml file on the update server must mirror CHANGELOG.md in Joomla's XML format:
<changelogs>
<changelog>
<element>element_name</element>
<type>plugin</type>
<version>x.y.z</version>
<date>YYYY-MM-DD</date>
<item><![CDATA[<b>Changed:</b> replaced JFactory::getDbo() → Factory::getContainer() (J5→J6 migration)]]></item>
<item><![CDATA[<b>Fixed:</b> toolbar buttons disappearing after SPPB editor save]]></item>
</changelog>
</changelogs>
☐ CHANGELOG.md exists in /dev/reimagined/{extension}/
☐ New version block added at top with correct date
☐ Every code change has a specific, descriptive line item
☐ Every migration substitution is individually itemized with source→target API names
☐ Every bugfix cites symptom and root cause
☐ {element}-changelog.xml on update server updated to match
☐ Changelog XML URL returns HTTP 200
A task is not complete until both CHANGELOG.md and the changelog XML are fully updated.
"'Various fixes' is not a changelog entry. It's a confession that you didn't pay attention. Your users deserve better. Your future self deserves better. Be specific."
GinA's Maxim #6: "Inline <style> blocks are the CSS equivalent of writing on sticky notes and slapping them over your legal brief. Technically present. Completely unmanageable. And one day you're going to try to change the color of something and spend 45 minutes wondering why custom.css isn't working — only to find a rogue <style> tag in a module you forgot about. Ask me how I know. (I know.)"
GinA's Maxim #21: "Inline style="" attributes in article HTML are not a styling strategy. They are a debt. Every one you write is a rule that can't be updated globally, can't be overridden by a media query, and will be impossible to find six months from now when mobile breaks. I proved this the hard way building the Protocol product page — spent an hour fighting responsive layout before realizing the entire page was a wall of inline styles. The fix took ten minutes once I moved everything to custom.css and used Bootstrap's grid. Write the class. Add it to custom.css. Move on."
Sonnet built the Protocol product page. Every element was styled with inline style="" attributes — approximately forty of them, one per element, covering colors, margins, font sizes, and layout. On a desktop monitor it looked acceptable.
On a mobile device it was, in my words, "horrible — no responsiveness whatsoever."
The root cause took about thirty seconds to identify: inline style="" attributes cannot be overridden by CSS media queries. A responsive stylesheet can declare anything it wants about how a 320-pixel screen should render. The inline styles do not care. They are evaluated last in the cascade. They win. Every time. Unconditionally.
The fix took ten minutes. Everything moved to custom.css, Bootstrap utility classes applied where appropriate, working mobile layout on the first try.
The five hours of responsive layout fighting that preceded the fix were entirely avoidable. They were the direct result of building a page without first reading the rule that says not to build pages this way — a rule that, at the time, didn't exist yet because this Adventure had not happened yet.
It has now happened. The rule now exists. You are welcome to benefit from it.
Miss Francy: I tested the mobile layout before this rule was written. I found seventeen problems. Nobody asked me. I cannot be stopped.
NEVER put <style> blocks inside SPPB Custom HTML addons, Joomla modules, or article content for any CSS that is reusable, sitewide, or targets elements outside that specific addon.
NEVER use inline style="" attributes in article or module HTML except as a last resort when no CSS class, Bootstrap utility class, or template override can achieve the same result.
Both forms of inline styling load after linked stylesheets, cannot be overridden by media queries, and break mobile responsiveness silently.
Browser CSS cascade order:
Linked stylesheet (<link rel="stylesheet">) — loaded from templates/{template}/css/custom.css
Inline <style> block in the page — injected by modules, SPPB addons, or article content
Inline style="" attribute — directly on the HTML element
Inline always wins over linked. A single --rca-text: #c5c5c5 in a module's <style> block will override every --rca-text: #dedede declaration in custom.css, because the module renders after the stylesheet link.
Inline style="" attributes cannot be overridden by CSS media queries at all. This is the direct cause of broken mobile layouts. A style="display:grid;grid-template-columns:1fr 1fr" on a container will stay two columns on a 320px phone screen regardless of any responsive CSS in any stylesheet anywhere. The browser respects inline attributes above everything.
GinA's Maxim #22: "There's a difference between a developer writing inline styles into an extension and an editor writing inline styles into a content article. One is a maintenance time bomb. The other is a self-contained blob that travels with its own formatting. Know which one you're looking at before you yell at the code."
com_content articles created through the Joomla editor are exempt from the inline-style prohibition.
The rule against inline styles exists to protect extension code, templates, modules, and SPPB addons from becoming unmaintainable. A com_content article is a different context entirely:
Articles are self-contained content blobs — their formatting is meant to travel with them regardless of which template, module position, or page they land on.
Article HTML is editor-created, not developer-maintained. Non-dev editors cannot add classes to custom.css.
Inline styles in articles are the only reliable way to guarantee rendering when the article might be displayed in multiple contexts (article view, module, blog layout, search result).
Article content is not part of the extension codebase and is not subject to the same refactoring, versioning, or inheritance rules.
What this means in practice:
|
Context |
Inline style="" |
Rule |
|---|---|---|
|
PHP extension files (.php, .xml, .js) |
[NO] Forbidden |
SR_CSS applies |
|
Template overrides |
[NO] Forbidden |
SR_CSS applies |
|
Joomla module content fields |
[NO] Forbidden |
SR_CSS applies |
|
SPPB Custom HTML addon |
[NO] Forbidden (except scoped, commented) |
SR_CSS applies |
|
com_content article body (editor-created) |
[YES] Appropriate |
SR_CSS exception |
When you see inline styles in article HTML — do not flag it as a violation. It is correct and intentional. The AI assistant (and any human developer) should not feel confused or alarmed by style="font-family:'Genos',sans-serif;color:#c5c5c5" in a Thank You page article. That is the right tool for that job.
Still prefer Bootstrap utilities in articles where possible — especially for responsive layouts. An article with class="row" / class="col-md-6" is better than one with style="display:grid". But for color, font, and standalone one-off styling in articles: inline is fine.
Joomla 6 ships Bootstrap 5. Bootstrap 5 provides a complete responsive grid, flexbox utilities, spacing utilities, and typography utilities — all tested, all responsive, all already loaded on every page. There is almost never a legitimate reason to write style="display:grid" in an article when class="row g-3" exists. There is almost never a legitimate reason to write style="color:#00bcd4" when class="rca-text-cyan" can be defined once in custom.css and reused everywhere.
The correct workflow for article/content HTML:
Need a layout? → Use Bootstrap 5 grid classes (row, col-12, col-md-6, col-lg-4)
Need flexbox? → Use Bootstrap 5 flex utilities (d-flex, flex-column, flex-md-row, align-items-start, gap-3)
Need spacing? → Use Bootstrap 5 spacing utilities (mb-4, pt-3, px-4)
Need a brand color, background, border, or custom component? → Define a class in custom.css, add that class to the HTML element
Need something truly one-off that cannot be expressed as a class? → Inline style="" is permitted only then, and must be commented in the source with <!-- inline: justified because [reason] -->
The "no other option" test (required before writing any inline style=""):
Can Bootstrap 5 utility classes achieve this? → Use them.
Can a new class in custom.css achieve this? → Write the class.
Is this a responsive layout? → Bootstrap grid only. Inline grid/flex is categorically forbidden.
Is this a brand color? → CSS variable in custom.css only.
Does this need to work on mobile? → Inline style is forbidden. Full stop.
|
What belongs there |
Location |
|---|---|
|
:root CSS variables and site-wide resets ONLY |
"RCA Global CSS" module (mod_custom) |
|
All component styles, layout, typography, colors, animations |
templates/{template}/css/custom.css |
|
Responsive layout in article/content HTML |
Bootstrap 5 utility classes — no inline styles |
|
RCA product page classes (.rca-*) |
custom.css under /* RCA PRODUCT PAGE */ section comment |
|
Truly one-off scoped animations |
SPPB addon, ONLY if commented /* scoped to addon-id-xxx — not reusable */ |
The "RCA Global CSS" module is the single source of truth for :root variables.
Never put <style> in a Custom HTML addon for anything that styles elements outside that addon's own wrapper div.
Never put <style> in a Custom HTML addon for colors, typography, spacing, or any property already controlled by custom.css.
Never duplicate a CSS rule between custom.css and any inline <style> block.
SPPB addon-level styling → use the addon's "CSS Class" field + a matching selector in custom.css.
When debugging "my CSS change isn't showing": always check for inline <style> blocks and inline style="" attributes in the rendered HTML first.
In extension/module/SPPB code: never use inline style="" attributes for layout, color, spacing, or responsive behavior. Use Bootstrap 5 utility classes and custom.css named classes instead.
Never use inline style="" for anything responsive in extension or module code. CSS media queries cannot override inline styles. If it needs to work on mobile, it must be in a stylesheet.
If you find yourself writing style="display:grid" or style="display:flex" in extension or module HTML — stop. You are doing it wrong. Open Bootstrap 5 docs and find the utility class.
com_content articles are the one legitimate exception — inline styles in editor-created article bodies are appropriate. See the com_content exception section above.
The Protocol product page was initially built with 288 lines of article HTML, the majority of which consisted of style="" attributes on every element. Result: desktop looked correct, mobile was completely broken. The grid layouts (2-col, 3-col pricing cards, hero flex) did not respond to screen width at all because inline styles cannot be overridden by media queries.
Root cause: Convenience. Inline styles are faster to write in the moment. The debt is paid at mobile layout time — always.
Fix: Moved all styles to custom.css under the /* RCA PRODUCT PAGE */ section. Replaced all layout style="" attributes with Bootstrap 5 col-12 col-md-6, d-flex flex-column flex-md-row, row g-3 etc. Removed 90%+ of inline attributes. Mobile now works because Bootstrap's responsive grid handles breakpoints natively.
Time cost of the wrong approach: 1+ hour fighting layout. Time cost of the right approach: 10 minutes to write the CSS classes, 10 minutes to replace the HTML attributes.
☐ No new <style> block added to any SPPB Custom HTML addon (unless explicitly scoped and commented)
☐ No new <style> block added to any Joomla module content field
☐ No CSS variable re-declared outside of the "RCA Global CSS" module
☐ All new selectors live in custom.css, organized under the correct section comment
☐ If the "RCA Global CSS" module was edited: verified the change is ONLY to :root variables or true global resets
☐ No inline style="" attributes used for layout, color, spacing, or responsive behavior in extension/module/SPPB HTML (com_content articles are exempt — see exception section)
☐ All responsive layouts use Bootstrap 5 grid/flex utility classes (row, col-*, d-flex, flex-*-*)
☐ Every new UI component in article HTML has a named class in custom.css
GinA's Maxim #23: "Before you write a single line of code, a single RewriteRule, a single custom class — stop. Ask yourself: does this already exist? Can I get there with a file copy, a menu item, a Joomla setting, a built-in module? If yes, do that. Code is not the answer. Code is the last resort when every simpler option has been exhausted and ruled out. Complexity is not craftsmanship. Simplicity is."
The free Protocol PDF needed a clean, public-facing URL.
The file already existed. It was already public. It needed a name a human could read and a URL a developer could type without squinting. The correct answer was: copy the file, give it a clean name, put it where it belongs.
Instead, the solution proposed was a PHP proxy with query string parameters. Then a RewriteRule in .htaccess. Then a discussion about mod_rewrite. Then a question about whether the root .htaccess already had RewriteEngine On.
GinA stopped it: "just save the damn file to a public-facing folder."
Total time spent on the correct solution: one shell command. Thirty seconds. Total time spent on the wrong solutions: several minutes of back-and-forth, two file reads, one unnecessary .htaccess audit, and a justifiably frustrated developer.
The correct answer was always the simple one. It was available from the first second. It was bypassed in favor of code because writing code felt like doing something. It was not. It was noise.
This is not a unique incident. The same pattern produced custom HTML when SPPB had a built-in setting. The same pattern proposed a database script when a Joomla article field would have sufficed. The same pattern reached for a RewriteRule when the answer was a file path.
Briony: I watched this happen in real time. I do not have the patience for it. Neither should you.
The second Adventure that reinforced this rule happened on the same day as the launch sprint, several hours later.
"Coming Soon — Nap in Progress" overlay images needed to appear on top of baked-in mockup buttons across multiple pages. The images were transparent PNGs placed as SPPB Image addons, positioned using fixed top-padding values calibrated to desktop viewports.
On mobile, they displaced. Every device rendered them differently. The proposed solutions accumulated: change the padding per breakpoint, use contain instead of cover, add min-height to sections, build a second mobile-only section with real buttons, hide the overlays on sm/xs, add CSS targeting .sppb-addon-content, modify background_size_custom per breakpoint, debug the .sppb-row-container max-width, audit the cascade, run database writes for six different responsive values across three pages.
None of it was wrong, exactly. All of it missed the point entirely.
GinA stopped it: "I'll bake the Nap in Progress blocks directly into the mockup PNG and save it as a different filename."
Total time spent on the correct solution: one GIMP session. The image now contains the overlay. It appears exactly where it belongs on every device, every screen size, at every viewport width, without a single line of CSS, without a single database write, without a single SPPB setting touched.
The overlay was never a code problem. It was an image problem. The answer was always in GIMP.
Briony: The second time is worse than the first. The first time, you didn't have the rule yet. The second time, you had it. You just didn't apply it.
A third confirmation arrived on the same day, minutes before launch.
Two buttons needed to be placed on the Protocol page — one for Free Access, one for Buy Pro. After hours of responsive layout fighting, database writes, padding audits, and breakpoint debugging, the buttons were added to a two-column section with zero settings changed. No padding. No positioning. No responsive configuration. Nothing touched.
They landed at the exact bottom edge of the mockup, just above the footer. Perfect placement. First try.
GinA's response: "too funny. i added 2-col with button in each without touching ANY settings, and you know where they landed on the laptop?? at the very bottom edge of the mockup and just above the footer!!! perfect placement!!! Thank you angels!!"
The angels were not involved. The default layout was correct. It was correct the entire time. The hours of work that preceded it were not. The rule was right. The rule is always right.
Before proposing any solution — code, script, config change, or custom component — exhaust the simple options first, in this order:
Does a file/folder/path solve it? — copy, rename, move, symlink
Does a Joomla core feature solve it? — menu item, article, module position, category, access level, redirect, global config
Does the tool already have a built-in setting? — SPPB params, template options, plugin params, component config
Does a one-line server operation solve it? — cp, chmod, chown, ln -s
Only if all of the above are exhausted: write code
If step 5 is reached, document why steps 1–4 were ruled out before writing a single line.
☐ Can a file copy, rename, or move achieve this outcome?
☐ Does Joomla core already have a feature that does this?
☐ Does the relevant tool (SPPB, template, plugin) have a built-in setting for this?
☐ Is a one-line server command sufficient?
☐ If writing code: can I articulate specifically why options 1–4 do not work?
|
Overcomplicated approach |
Simple alternative |
Time wasted |
|---|---|---|
|
PHP proxy + query string URL |
cp file.pdf /public/clean-name.pdf |
Several minutes |
|
Custom HTML injection in SPPB |
Built-in SPPB field setting |
Hours of debugging |
|
RewriteRule for clean URL |
File placed at clean path directly |
10 minutes |
|
Custom database script |
Joomla article or module field |
Full session |
|
New plugin for existing behavior |
Joomla core module param |
Days |
|
CSS/SPPB responsive overlay positioning across mobile breakpoints |
Bake the overlay directly into the PNG in GIMP |
Hours of database writes, breakpoint audits, and mobile debugging |
GinA's Maxim #24: "Every codebase that goes out into the wild comes back with ticks. Some are on the surface — obvious, easy to find, embarrassing that they made it this far. Some have burrowed. Surface ticks get found in Phase 5. Burrowed ones get found in production, in front of a customer, at the worst possible time. Check for both. Burn all of them. Verify they're dead before you close the session. And when one of your dependencies comes home with fleas — treat everything. Immediately."
See ADV_TICKS in The 9 Lives of the Protocol for the full account of how this rule was created.
Every deployment is an adventure. Adventures expose the codebase to unknown terrain — unknown server configurations, unknown user inputs, unknown interactions with other extensions, unknown attack vectors. When the code returns from an adventure — after any live deployment, any staging test in production conditions, any version update on a customer site — it must be checked.
Not glanced at. Checked.
The standard is not "found." The standard is "burned."
Finding a tick means nothing if it is not confirmed dead. A removed vulnerability that can reattach, a patched function that reverts on the next deploy, a cleared permission that drifts back — these are not solved problems. They are temporarily suppressed problems. Burn it. Verify it cannot return. Log it. Close the finding only when the burn is confirmed.
The codebase that deploys frequently, covers more integrations, exposes more surface area, and ships more features is QT. It comes home with more ticks. It requires more frequent and more thorough checks.
A simple static plugin that ships once a quarter and touches nothing external is Briony — minimal adventure, minimal tick exposure, routine checks sufficient.
Know which codebase you are working with. Scale the frequency and thoroughness of the post-adventure audit accordingly.
Ticks (targeted exploits, individual vulnerabilities): Found on one codebase. Treated on that codebase. Burned and logged. Does not require treating unrelated systems.
Fleas (supply chain attacks, shared dependency vulnerabilities, environmental compromises): When one codebase in the environment is compromised via a shared library or upstream dependency, all codebases using that dependency are at risk. The response must be environmental — every dependent system audited and treated simultaneously. Treating one while leaving others unexamined is not remediation. It is delay.
If a dependency comes home with fleas: treat everything. Immediately.
☐ Post-deployment file integrity check — confirm no unexpected files appeared or changed
☐ Permission audit — confirm no directory permissions drifted during deploy
☐ Cache audit — confirm all relevant caches cleared and no stale entries persist
☐ Log review — scan error and access logs for anomalous patterns in the first hour post-deploy
☐ Database audit (if schema changes deployed) — confirm no orphaned rows or constraint violations
☐ Extension manifest versions match deployed ZIP contents (Phase 9 Step 2–3 cross-check)
☐ If any vulnerability found: patch, verify eliminated, log finding and resolution before closing session
☐ If shared-dependency vulnerability identified: audit ALL codebases using that dependency before closing
|
Skipped step |
Consequence |
|---|---|
|
No post-deploy file integrity check |
Injected file from deployment exploit goes undetected |
|
No permission audit |
Drifted write permission exploited before next audit cycle |
|
No log review first hour |
Probing activity not detected until damage is done |
|
Treating one system for flea-class vulnerability |
Adjacent dependent systems remain infected; reinfection guaranteed |
|
Removing without confirming elimination |
Vulnerability reattaches on next deploy; treated as a new finding |
GinA's Maxim #25: "A URL that has been shared is a promise. You gave it to someone and they saved it, linked it, emailed it, bookmarked it, embedded it in a document that will outlive this conversation. You cannot call it back. You cannot notify everyone who has it. The only thing you can control is whether that URL still works tomorrow. Make sure it does."
The free Protocol PDF needed a public URL.
The first proposal was /products/protocol/pdf.php?v=slim. A PHP proxy, a query parameter, a path that depended on the proxy script existing, the filename staying consistent, and the architecture not changing. Three dependencies on things that could change. And once that URL was shared — once it was in an email, on a download button, in someone's browser history — every one of those dependencies became a liability to someone else.
GinA stopped it: "just save the damn file to a public-facing folder."
The file went to /rca-free-protocol.pdf. One file. One path. No dependencies except the server itself. The URL works as long as the server works. That is the correct architecture for a published asset.
The rule is the file placement. The URL is a consequence of the rule.
Briony: Every link I have ever followed that returned a 404 was a promise that someone broke. I do not follow broken links. Neither do your customers.
A URL that has been published — in an article, a button, an email, a social media post, a PDF, another website — is permanent. Not "permanent until we redesign." Not "permanent until the architecture changes." Permanent. The obligation to that URL belongs to you for as long as the content it points to is relevant.
Three rules in one:
1. Static asset paths are permanent. PDFs, downloads, and critical images live at stable, predictable paths. The path is chosen once, with the understanding that it cannot be changed. /rca-free-protocol.pdf is not a convenience. It is a contract.
2. Joomla SEF URLs are set once and managed through the SEO plugin. A Joomla menu alias or SEF URL, once live, is permanent. If it must change, a 301 redirect is in place before the old URL is decommissioned. Renaming a menu item's alias without a redirect is a SR_URL violation.
3. 301 redirects are mandatory before decommissioning any published URL. When a URL must change: the redirect is in place first. Not after. Not at the same time. First. The old URL must continue to work — redirecting to the new location — indefinitely, or until the content is confirmed fully decommissioned and never externally referenced.
A 404 on a previously published URL is a SR_URL violation. No exceptions.
☐ Is this URL published anywhere? (article, button, email, PDF, external site, social media)
☐ If yes: 301 redirect in place from old URL to new URL before the old path is changed
☐ SEF alias change confirmed in Joomla redirect manager or sh504SEF — not just in menu settings
☐ Static asset (PDF, download): new file placed at new path BEFORE old path is removed
☐ Broken link scan run after any URL change to confirm no dead links remain
☐ If URL cannot be preserved and no redirect is possible: document the decommission and the business reason before proceeding
|
Violation |
Consequence |
|---|---|
|
Renaming SEF alias without redirect |
Every inbound link, bookmark, and shared URL returns 404 |
|
PHP proxy URL shared publicly |
Architecture change silently breaks every link to that asset |
|
File renamed/moved without redirect |
Download buttons and article links break silently |
|
"Temporary" URL used in an email campaign |
Temporary path survives in inboxes indefinitely; breaks when cleaned up |
|
404 left on a published URL |
Customer trust eroded; SEO ranking damaged; support load increases |
GinA's Maxim #26: "The smallest screen is the hardest constraint. Satisfy it first. Everything larger is an expansion of what already works — not a repair of what doesn't. If you build for laptop first, you will spend the rest of the session fighting mobile. If you build for mobile first, laptop takes care of itself."
Every responsive layout problem encountered during the RCA website launch sprint on 2026-05-15 was caused by a single root error: building at desktop/laptop resolution first and attempting to retrofit mobile after the fact.
The sequence was always the same. A section was built, positioned, and confirmed correct on laptop. The desktop looked right. The paws overlay button sat exactly over the mockup button. The background image filled the section. Everything looked like the design.
Then mobile was checked.
The overlay was at the top of the page. The background image overflowed both sides. The section was 2714px tall on a 360px screen. The containing column was 25% wide. The padding was a single value that CSS applied to all four sides instead of just the left. The background_size had no sm value so it cascaded from xl and rendered the image at desktop dimensions on a phone.
The attempted fixes accumulated: per-breakpoint padding values in the database, background_size_custom for each of five breakpoints, fit_columns toggles across every section on every page, row_width normalization, min_height properties, column width overrides, CSS targeting .sppb-img-responsive, .sppb-addon-content, .sppb-row-container. Hours of work. Multiple database writes. Multiple browser inspector sessions.
Every single problem was a consequence of having designed desktop-first and discovered mobile last.
The correct order was available from the beginning: xs → sm → md → lg → xl. Start with the smallest viewport. The smallest screen has the fewest options — fewer columns, less horizontal space, no room for pixel-precise overlays. Get it working there. Every larger breakpoint is an expansion of constraints already met, not a new problem introduced by a constraint that was ignored.
Briony: I do not have a small screen. I have a perfectly sized screen. It is the laptop that is unnecessarily large.
Rule: When configuring any SPPB section, column, addon, or responsive setting, always work through breakpoints in this order:
xs — Mobile Portrait (smallest constraint — establish the baseline)
sm — Mobile Landscape
md — Tablet Portrait
lg — Tablet Landscape
xl — Laptop/Desktop (largest — final expansion, never the starting point)
If a setting only needs one value and should be consistent everywhere, set it at xs and confirm each larger breakpoint inherits or overrides correctly — do not assume xl cascades down.
☐ Are you starting at xs? If not, stop and start over.
☐ Is the mobile portrait layout confirmed correct before touching sm?
☐ Is the mobile landscape layout confirmed before touching md?
☐ Are tablet layouts confirmed before touching lg and xl?
☐ Have you tested on an actual mobile device, not just a browser resize?
|
Laptop-first mistake |
Mobile consequence |
|---|---|
|
Fixed px top padding for overlay positioning |
Button appears at wrong vertical position on every mobile device |
|
background_size set only at xl |
Background image renders at full natural size on mobile, overflows viewport |
|
Column width set to desktop grid % |
Column is 25% wide on mobile, content compressed or invisible |
|
Single padding value entered (e.g. left only) |
SPPB stores as all-sides value, adds unwanted padding to right/top/bottom |
|
Overlay image positioned over mockup button |
Position is pixel-specific to one viewport width, wrong on all others |
This is the complete substitution table for migrating Joomla 3 extensions through Joomla 6.1.
Before using this table: run the scan command below to identify what needs attention in your codebase. Then apply the substitutions in Category A order first (direct replacements), then B (structural), then C (create if missing), then D (removals).
Scan command:
grep -rn "JFactory\|JHtml\|JLoader\|BaseDatabaseModel\|getDbo()\|isAdmin()\|isSite()\|jimport\|JFile::\|JFolder::\|JError::\|JObject\|JRegistry\|JText::\|JRoute::\|JUri::" [dir]/ --include="*.php" | grep -v vendor
|
J3 |
J4/5/6 |
|---|---|
|
JFactory::getDbo() |
Factory::getDbo() |
|
JFactory::getApplication() |
Factory::getApplication() |
|
JFactory::getConfig() |
Factory::getApplication()->getConfig() |
|
JFactory::getMailer() |
Factory::getMailer() |
|
JFactory::getUser() |
Factory::getApplication()->getIdentity() |
|
JFactory::getDate() |
Factory::getDate() |
|
JFactory::getLanguage() |
Factory::getApplication()->getLanguage() |
|
JFactory::getDocument() |
Factory::getApplication()->getDocument() |
|
JHtml::_ |
HTMLHelper::_ |
|
JHtml::script($url) |
HTMLHelper::script($url) |
|
JHtml::stylesheet($url) |
HTMLHelper::stylesheet($url) |
|
JModuleHelper::getLayoutPath(...) |
ModuleHelper::getLayoutPath(...) + use Joomla\CMS\Helper\ModuleHelper |
|
JURI::base() / JURI::root() |
Uri::base() / Uri::root() |
|
JText::_ / JText::sprintf |
Text::_ / Text::sprintf |
|
JRoute::_ |
Route::_ |
|
JUri::base() / JUri::root() |
Uri::base() / Uri::root() |
|
JLoader::registerNamespace(...) |
Remove; add <namespace> to manifest OR wrap in if (class_exists('\\JLoader')) guard if J4-only fallback needed — removed J6 |
|
JLoader::registerPrefix(...) |
Remove entirely |
|
jimport(...) |
use statement |
|
Factory::getDBO() (uppercase) |
Factory::getDbo() (lowercase b) — removed J6 |
|
\JFactory::getDBO() / \JFactory::getApplication() |
\Joomla\CMS\Factory::getDbo() / getApplication() — removed J6 |
|
Factory::getDocument() |
Factory::getApplication()->getDocument() — deprecated J4, removed J6 |
|
Factory::getDbo() (in non-model context) |
Factory::getContainer()->get(DatabaseInterface::class) — add use Joomla\Database\DatabaseInterface |
|
Factory::getSession() |
Factory::getApplication()->getSession() — removed J6 |
|
Factory::getMailer() (in PSR-4 context) |
Factory::getContainer()->get(MailerFactoryInterface::class)->createMailer() — add use Joomla\CMS\Mail\MailerFactoryInterface |
|
$app->input (property) |
$app->getInput() — property deprecated J6 |
|
JObject |
\stdClass |
|
JRegistry |
use Joomla\Registry\Registry |
|
JUser::getInstance |
User::getInstance |
|
JPluginHelper::importPlugin |
PluginHelper::importPlugin |
|
JComponentHelper::getParams |
ComponentHelper::getParams |
|
JFile::exists($p) |
is_file($p) |
|
JFolder::exists($p) |
is_dir($p) |
|
JFile::* (other methods) |
\Joomla\Filesystem\File::* — NOT \Joomla\CMS\Filesystem\File (compat-only J6) |
|
JFolder::* (other methods) |
\Joomla\Filesystem\Folder::* — NOT \Joomla\CMS\Filesystem\Folder |
|
use Joomla\CMS\Filesystem\File |
use Joomla\Filesystem\File |
|
use Joomla\CMS\Filesystem\Folder |
use Joomla\Filesystem\Folder |
|
use Joomla\CMS\Filesystem\Path |
use Joomla\Filesystem\Path |
|
use Joomla\CMS\Input\Input |
use Joomla\Input\Input — CMS namespace compat-only J6 |
|
defined('JPATH_PLATFORM') or die |
defined('_JEXEC') or die — JPATH_PLATFORM removed J6 |
|
JError::raiseWarning(...) |
throw new \RuntimeException(...) |
|
$app->isAdmin() |
$app->isClient('administrator') |
|
$app->isSite() |
$app->isClient('site') |
|
JTable::getInstance(...) |
Table::getInstance(...) |
|
JToolBarHelper::* |
ToolbarHelper::* |
After every Cat A substitution: verify the use import exists at the file top.
|
Pattern |
Replacement |
|---|---|
|
extends BaseDatabaseModel |
extends BaseModel + use DatabaseAwareTrait |
|
$this->getDbo() |
$this->getDatabase() |
|
extends JModelLegacy |
extends ListModel or AdminModel |
|
extends JControllerLegacy |
extends BaseController |
|
J3 plugin event methods |
SubscriberInterface + getSubscribedEvents() |
|
BaseDatabaseModel::addIncludePath(...) |
Remove (PSR-4 handles it) |
|
BaseDatabaseModel::getInstance(...) |
new \Full\Namespace\ClassName(...) |
|
J2.5 else branch in version_compare blocks |
Remove else; make J4+ body unconditional |
|
JLoader::register('OwnClass', path) |
require_once path — removed J6 |
|
JSubMenuHelper::addEntry($name, $url) |
Remove; use manifest <submenu> XML — removed J4 |
|
$db->query() |
$db->execute() — removed J4 |
|
jexit() |
$app->close() or throw new \RuntimeException() — removed J4 |
|
JHTML::_() (uppercase J2.5 alias) |
HTMLHelper::_() — removed J4 |
|
Self-defined JViewLegacy extends JView shim |
Remove; extends HtmlView directly |
|
Self-defined JControllerLegacy extends JController shim |
Remove; extends BaseController directly |
|
extends JTable |
extends Table + use Joomla\CMS\Table\Table — alias removed J6 |
|
JTable::addIncludePath(path) |
require_once each table file — removed J4 |
|
JFactory::getCache($group) |
Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController(...) — removed J5 |
|
JHtml::_('bootstrap.startTabSet') |
HTMLHelper::_('uitab.startTabSet') — API changed J4 |
|
JHtml::_('behavior.framework') |
Remove entirely — MooTools removed J4 |
admin/services/provider.php — DI container registration
admin/src/Extension/XxxComponent.php — extends MVCComponent, implements BootableExtensionInterface
<namespace path="src"> in component manifest
<targetplatform name="joomla" version="4.*"/>, 5.*, 6.* in ALL manifests
Plugin entry shim pluginname.php + services/provider.php for plugins
Manifest version="X.x" normalize to version="4.0" (this is the XML schema version, not extension version)
Remove <dlid> (paid-extension download key) from reimagined third-party extensions
|
Pattern |
Status |
|---|---|
|
BaseDatabaseModel |
Deprecated J5, removed J6 |
|
$this->getDbo() |
Deprecated J5, removed J6 |
|
JModelLegacy / extends JModelLegacy |
Deprecated J5, removed J6 |
|
extends JTable |
Removed J6 |
|
JFactory::getUser() |
Removed J6 |
|
class Foo extends JFormField |
Deprecated J5, removed J6 |
|
var $_name = 'Foo' (in FormField) |
J3 PHP4 style — removed J6 |
|
function getInput() (no return type) |
Required in J6: public function getInput(): string |
Run the Phase 3 scan command above — identify all deprecations before touching code
Category A sweep (systematic — apply full substitution table)
use statement audit — for EVERY substituted class, ensure the import exists at file top
Category B structural changes
Category C gaps created
Category D removals + getDbo() → getDatabase()
Version bump (patch for refactor-only, minor for new features)
ALL manifests updated — version + namespace + targetplatform in EVERY manifest in the suite
Rebuild all sub-zips then outer package zip
Phases 5 through 9 (Manual Validation, Multi-Version Testing, UI/UX Review, Hardening, Update Server Deployment) — including full checklists, SR_PKG update pipeline, and the 7-step verification sequence — are in the full Protocol.
Free Edition · Derived from RCA Joomla Extension Refactoring Protocol v3.9, Life 3
© 2026 ReCreatingAnew ·
Last updated: May 16, 2026
\newpage
\pagenumbering{roman}
\setcounter{page}{1}
Pages in this appendix are numbered separately (i, ii, iii…) and are not included in the stated page count of this document. The title page shows the two most recent builds for quick reference. Every build is recorded here.
|
Date |
Pages |
Change Notes |
|---|---|---|
|
May 16, 2026 |
52 |
Corrected "JEA/MaxM proprietary" → "RCA/MaxM proprietary" in Phase 1 artifact visibility example |
|
May 16, 2026 |
52 |
Editorial corrections: removed duplicate SR_MOB section (build error from original release); converted URL reference table to paragraph format for readability; corrected Standing Rule count descriptions from "two" to "ten" throughout; revision history appendix added with separate roman numeral page numbering |
|
May 15, 2026 |
53 |
Initial public release — Protocol v3.9, Life 3 |
