Repository navigation
v0.45.0
Everything on main since v0.44.0, again with no pull request left open behind it: three things built, and a run of fixes.
A release is installed from the admin. On CoreX → Releases a site on a host with no command line installs its next release, and goes back to the one before. It has not been run on a shared host yet: make the first one a release that changes nothing important, with somebody watching. This release itself still has to reach a site the old way.
A reply is a designed email. It is written in an editor, previewed as the email it will be, sent with a plain-text version beside it, in the site's own colours, logo and footer or its own template, and kept on its submission. The reply box says, before anything is typed, when a reply will not be delivered.
Several data models are exported in one go, and a column that holds personal data leaves the site in a file only with a person allowed to export personal data. Until now subscribers, job applicants, login attempts and the activity log's actors were exported with no confirmation and no right asked (Security, below).
Fixed, mostly in dialogs: the corex/modal and corex/drawer blocks (closed by a press that began inside them, larger than the window, title and close button scrolled away, no name without a title, nothing to read with scripts off), the admin's own dialogs closed the same way, and a secondary button drawn with two borders.
Read Client impact before a site takes this. What acts on a running site without being asked: a person who is not an administrator loses the personal columns in a Data export until given "Export personal data"; Data export files older than 30 days are removed at the first daily clean-up; on a production site with no delivery provider, or with live delivery off, Send reply is switched off; a reply's email looks different; and a cached page holding a corex/modal or a corex/drawer has its old markup under the new stylesheet until the cache is cleared. A site that implements SubmissionEmailGateway or DataExportStore itself has methods to add. Building needs a rebuild of plugins/corex-config and of addons/corex-ui.
Added
- A release is installed from the admin (spec 107, slice 4). On CoreX → Releases a package that was read and not refused can be installed. The screen states what installing does and gives the recovery link, to copy or save as a file, before it asks for the version to be typed. Then the site unpacks the package beside itself, checks each folder against the package's own measure, copies CoreX's tables as they are, and puts the release in place by renames written down first. The request after that is answered by the release just installed: it brings the database to the release and lists what moved, and only then is the release recorded as the site's. Each step is its own request, the screen says which one the site is at, and an installation whose browser went away is gone on with by whoever next opens the screen and may install releases (DECISIONS #307).
- A folder a release owns is replaced whole. A file the release before shipped and this one does not is not left on the site: it is in the kept previous release. A test is named for the day this was missing (2026-10-11, pattern files of an earlier theme left by extracting a zip over it). A folder the running release owned and the package does not hold leaves the site with the previous release, and the screen names each before the administrator confirms.
COREX_ALLOW_RELEASE_INSTALL. A site that setsDISALLOW_FILE_MODScould not install a release, and the screen told it to lift the setting. One line inwp-config.php,define( 'COREX_ALLOW_RELEASE_INSTALL', true );, now allows a release to be installed from the Releases screen and nothing else: WordPress's own installers, updaters and editors stay off. The screen gives the line where it is needed.--with-plugin=<folder>on the package builder. A plugin that is neither CoreX's nor the site's own (a mail plugin, say) is named tonpm run build:dist, any number of times, and is then a folder of the release like the others: listed, measured, checked on the site and replaced whole.corex-release.jsonlists such plugins underthird_party_plugins.- A visitor is held off while a release's files are in place and its database is not. WordPress's own maintenance answer, for those seconds, from the swap until the finishing request; it lets through the one request carrying the installation's key.
- A copy of CoreX's tables before a release changes them, one for each table in the schema registry, named
<table>_cxrel_<yyyymmddhhmm>. Kept for a person to read from; nothing puts one back. One set is kept: an installation removes the copies of the one before it. - Going back to the release before, from the screen (spec 107, slice 5). CoreX → Releases shows which release is kept and which replaced it, says what going back does not undo, and puts the previous release's folders back in one action confirmed by typing its version. No change to the database is reversed, and the answer says so (DECISIONS #310).
- A return made through the recovery link is found. The link works without CoreX, so CoreX cannot hear of it. When the Releases screen is next opened it finds the folders were put back, records the return as one nobody is known to have made, sets the site's record of its release right, and removes the newer release's folders and the recovery file.
- What is kept goes after its time, when the screen is opened. A copy of the tables after 14 days; a package, or part of one, that was sent and never installed after 7. No scheduled job does this: it happens when somebody opens Releases.
- What happened here. The screen lists each installation, refusal, return and installation that did not complete or was given up, newest first, with who did it and when.
- The screen says it is not a backup, where the running release is stated.
- Two guides for a host with no command line (spec 107, slice 6).
docs/en/05-deployment/releases-from-the-admin.md: building the package, a third-party plugin in it, the line for a site that setsDISALLOW_FILE_MODSand what it gives, what is replaced and what is not, the database step, the way back and what it does not undo, each refusal, and what is left on the host.first-installation-by-hand.md: the one installation that is still done by hand, and how a site that has been updated by unpacking zips moves to the screen. The other deployment pages point to them. - The reply box says, before anything is typed, when a reply will not be delivered, and where that is changed (spec 106, slice 1). On a production site a reply is delivered only when CoreX → Settings → Mail has a delivery provider chosen and live delivery on. Nothing beside the box said so: a reply written on a site without them was refused. The Reply section now says which of the two is missing, links somebody who may change settings to the Mail settings, tells anybody else who can, and switches Send reply off until it is set. On a development site, and a staging site with live delivery off, it says a reply is kept in Email Studio and delivered to nobody. The statement comes from the decision the sending itself takes, so the two cannot disagree (DECISIONS #305).
- A reply arrives as a designed email, with a plain-text version beside it (spec 106, slice 2). The site's logo, or its name, above the message on a card with CoreX's brass edge, and a footer naming the site and linking to it. It holds at phone width, has a dark palette for a mail client that asks for one, reads right to left on a right-to-left site, and lets each paragraph take its own direction, so an Arabic line in an English reply starts from the right. The same words go with it as plain text, with links written out and list items marked, as the alternative part of one message. The reply used to be wrapped in a bare shell that set no type, no colour and no footer, and was sent as HTML alone (DECISIONS #308).
- A site gives its emails its own colours, logo and footer, or its own template (spec 106, slice 4), from its own settings or its own plugin and with no framework file changed. CoreX → Settings → Mail has an Email accent colour, an Email logo and an Email footer line. The filter
corex_email_brandreaches the other colours. The filtercorex_email_designreplaces the template, andwp corex make:email-design <Name>writes a working copy of the standard one into the site's plugin to start from. A site's design that throws, answers nothing or leaves the message out does not stop a reply: the email goes in the standard design, and the people who manage email are told which design failed and how (DECISIONS #311). The guide is "A site's own email design". - A reply is written in an editor, and previewed as the email it will be (spec 106, slice 3). The message box has bold, italic, bulleted and numbered lists and links, from a toolbar or from the keyboard (Ctrl or Cmd with B, I and K; with Shift and 8 or 7 for the lists). A link is asked for in the page, and one that goes anywhere but the web, a mailbox or a telephone is refused there. What is pasted keeps that formatting and loses the rest. Each paragraph reads in the direction of what is written in it. Preview shows the email the server will send, in the site's design, at the width of a wide screen or of a phone, with its plain-text version; the email that is then sent is that one, to the byte. The box was a plain text area and there was no preview (DECISIONS #313). A draft kept between visits is not built yet.
- The colours and type of a CoreX email are defined once, in the email add-on (
Corex\Email\Design\EmailPalette,EmailBrand), for every email drawn from them. They were inside the Guides add-on's support message. - A reply is kept on its submission and listed there: its subject, its text, who sent it, when, and what happened to it. The history held an action and a state and nothing of what was said. A reply that was not sent is listed as not sent, with the reason.
- A model says which of its columns hold personal data, and of which kind (spec 109, slice 1).
ManagedTabletakespersonalData, a column's id to one of the kindsDataFieldhas: contact details, names and identities, what people wrote, sign-in and security records, interests and behaviour, sensitive details, or none. The ten tables CoreX and its add-ons ship declare theirs. CoreX → Data → Models says in words what each field holds, where it printed a key (DECISIONS #309). - One right to take personal data out of the site in a file: "Export personal data" (
corex_export_personal_data), an ability of its own that an administrator holds and CoreX → Access & Abilities can give. Managing data, data models or submissions does not carry it. - Docs: Personal data in exports, with its Arabic page, and a step in the in-admin guide for the Data screen.
- Several data models are exported in one go (spec 109, slice 2). CoreX → Data → Export lists the models a person may export, in two groups, each with how many records it holds, and ticks none. Two or more ticked models open one dialog and are written into one file: a workbook with a sheet for each, an archive with a CSV file for each, or a PDF with a section for each while their records together fit one. A model that holds no records is left out and the dialog says so; a model that goes away while the export is written is left out and named, and the rest are written. The tab had one dropdown named "Model" (DECISIONS #312).
- A Data export's history says what became of each file: the models it covered, the format, the records, the size, who made it, and when its file expires, expired or was deleted and by whom. A file can be deleted, after being asked again, and an export that was not finished can be taken on from where it stopped. A person sees their own exports and an administrator everybody's.
ManagedTabletakescategory,recordsorlog, for the two groups the Export tab lists models in. Left out, a table that can be written to is records and any other a log.- Docs: Exporting data, with its Arabic page.
Changed
- The pane says what happened to a reply: sent, and to which address; kept on the site and not delivered; or not sent, with the reason, and what was written left in the box. It said "Reply processed by Email Studio." for all three.
- What is posted as a reply is reduced to the formatting a reply has: paragraphs, line breaks, bold, italic, lists, and links to the web, a mailbox or a telephone. Anything else is removed and its words kept. The route used to refuse a body holding a script, an event attribute or a
javascript:address; it removes them now. - The Data export offers a column that holds personal data only to a person allowed to export personal data, says how many columns it is not offering, and refuses one from anybody else with a 403 whatever was sent. Its confirmation names the kinds of personal data in the columns chosen; it said "contains personal data" and named nothing.
- Every answer of a form submission is personal data on the Data screen, as it always was in the Submissions inbox. The Data screen guessed from an answer's key, so a name, a company and a budget were exported with no confirmation.
- The Submissions inbox asks for the same right. It let an administrator export personal columns and nobody else; it now lets whoever holds "Export personal data". The filter
corex_submission_export_personal_datastill has the last word. - A Data export's file is removed 30 days after the export was made, as a Submissions export's is; the entry stays. Nothing removed one before, while the notification for a finished export said the file "stays available until the export retention window ends".
- The notification for a finished Data export opens the Export tab. It opened the Data screen's first tab, with nothing to download in sight.
- The Export tab is shown to a person who manages data. It asked for the ability to manage data models, which no export asks for, so a person who could export from the Records tab was not shown the tab made for exporting.
Security
- Subscribers, job applicants, login attempts and the activity log's actors were exported from CoreX → Data with no confirmation and no right asked. Every column of every managed table was declared as holding no personal data, by the code that read the table and not by the table. A column is now what its table declared, and a column nothing was said about is treated as personal data. Read from the code on 2026-10-11 while specifying spec 109; nobody reported a file taken this way.
- The Data screen exported a submission's personal columns to anybody who managed submissions, where the Submissions inbox withheld the same records from everybody but an administrator. One rule now holds in both.
Fixed
- A modal or drawer block is not closed by a press that began inside it, or by a click on its own panel. On a page, pressing the mouse on a button inside a
corex/modalorcorex/drawer, sliding off it and letting go over the dimmed page closed the dialog, and so did a press begun on the dimmed page and let go inside. A click on the panel's own padding closed it as well, which in a drawer is all the height its content does not fill. Both blocks are now left by a press that starts and ends outside the panel. A click or a tap on the dimmed page, Escape and the close button close them as before, and focus goes back to the button that opened them. - A drawer block fits the window, and the end of what it holds can be read. A
corex/drawerwas taller than the window by twice its padding (752px in a 720px window), so a drawer holding more than fits stopped scrolling with its last 16px below the window's edge: most of the last line of text, or of a link reached with Tab. Its sizes now count its padding. On a desktop it is 384px wide where it was 416px, and on a phone it leaves 38px of the dimmed page beside it where it left 6px. Acorex/modalcounts its padding and border the same way: it kept inside the window by 2px, and on a phone a modal holding a paragraph was as wide as the window, with nothing beside it to tap. It is now at most 512px wide where it was 546px, 92% of a narrow window, and 19px clear of the window's top and bottom. - A modal or drawer block opens at its first line, and its title and close button stay in view. A
corex/modalorcorex/drawerwhose content held a link opened with focus on that link and scrolled to it: with the link at the end of a long text, a visitor was shown the last lines and never the title or the first ones. Scrolled by hand, the title and the close button went up with the text, and on a phone a visitor at the last line had the strip of dimmed page beside the dialog to close it with and nothing else. The title and the close button are now in a header that does not scroll, and the content scrolls under it. A dialog opens at its first line with focus on the close button. The close button is 30px square where it was 21px by 18px, and a long title wraps beside it where it ran under it. - A drawer block comes in from its own edge on a site read right to left. There the drawer at the end, which is on the left, began 16px inside the window and moved out to its edge.
- A modal or drawer block with no title has a name. A
corex/modalorcorex/drawergiven no title had no name for a screen reader to give: it was a dialog and nothing more. It now has the label of the button that opened it as its name, so the dialog opened by "Read the terms" is the dialog "Read the terms". One with a title is named by its title, as before. A block with no title and no trigger label is named "Open", which is a name and a poor one, so in the editor the Title field now says, while it is empty, what the dialog will be named in its place, and asks for a title when the trigger has no label either (DECISIONS #316). - What a modal or drawer block holds can be read with scripts off. With scripting off in the browser the block's button was shown and did nothing, and its content could not be reached, though the block's contract said it could. The dialog is now shown in the page where the block is, with its title, and the two buttons nothing answers are not shown. Not covered: a browser with scripting on in which the block's script was blocked or did not load.
- A dialog in the CoreX admin is not closed by a press that began inside it. Pressing the mouse on a button in a submission's pane, sliding off it and letting go over the dimmed page closed the pane, and a note being written in it was lost; the button's own action was not taken. Every dialog the admin draws with
CorexDialogdid this: the inbox's pane and its confirmations, the export dialogs, a record's detail on the Data screen and the changes to a role on Access & Abilities. A dialog is now left by a press outside it when the press both starts and ends outside. Escape and the close button are as they were. - Email Studio lists its captured emails when one of them cannot be read. One record whose contents had not survived being stored took the whole list with it, and the screen answered an error.
- Escape in a dialog opened from another closes that one only. It closed both: leaving a confirmation or a preview opened from the submission's pane took the pane with it.
- Lines posted as blocks a reply does not have stay apart.
<div>One</div><div>Two</div>, which is what a browser's editor and many pasted pages write, was reduced to "OneTwo" in one paragraph; each is now a paragraph, and so is each cell of a pasted table. - A paragraph pasted from a word processor is not broken in the middle of its sentences. A line end inside markup is how the markup was laid out, and is read as a space. Lines typed with no markup of their own stay lines, as before.
- What somebody types between angle brackets in a reply is kept. "Contact Sam sam@example.com" reached the customer as "Contact Sam", and "<500 EGP and >300 EGP" as "300 EGP": the text was read as markup and deleted.
- A reply that went and could not then be recorded is answered as sent, with a line saying it is not listed and is not to be sent again. The route could answer an error after the email had gone.
- The history says "Reply kept on this site, not delivered" for a captured reply, where it said "Reply sent", and "Email sent again" for a resent notification, where it said "Reply sent".
- A secondary button in the CoreX admin has one border, in the CoreX palette. WordPress draws a border of its own on these buttons in its admin accent: blue, red on a destructive button, pale grey on a disabled one. CoreX drew its line inside that one and left it alone, so "Add note", "Move to trash" and "Send reply" in a submission's pane, and the secondary buttons of the Data screen, Data Models and the export dialog, were outlined in a colour that is not CoreX's, in the light theme and the dark. No button changes size.
Client impact
- Clear the page cache after taking this release, on a site that uses a
corex/modalor acorex/drawer. Their markup changed and their stylesheet with it. A cached page holds the markup from before, and with the new stylesheet its title sits against the panel's edge and its close button under the content. - A site that styles these two blocks should look at them once. What changed:
- The title and the close button are in a new
<div class="corex-modal__header">(orcorex-drawer__header), and the close button comes before.corex-modal__body, not after it. A selector that reaches them as children of the<dialog>no longer matches. - The
<dialog>has no padding and does not scroll. The padding is the header's and the body's, and the body scrolls. Padding a site put on.corex-modalor.corex-draweris now added round both, outside the scrollbar: put it on the header and the body. - The close button is laid out in the header and is not positioned.
inset,toporrightset on.corex-modal__closeor.corex-drawer__closedo nothing now. - The title has no margin under it, and there is a line under the header. The text starts 8px lower than it did.
- The title and the close button are in a new
- By the keyboard, a long dialog is scrolled after one press of Tab. Focus opens on the close button, which is outside what scrolls, so the arrow keys and Page Down do nothing from there. Tab goes to the content, which takes focus while it holds more than fits, and the keys scroll it. Until now they scrolled at once, and took the title and the close button away.
- A
corex/drawerand acorex/modalare narrower by their own padding: the text column of each is 32px narrower on a desktop (34px in a modal, with its border), and a modal that fills its height is 34px shorter. A site that set a width or a height on.corex-draweror.corex-modalin its own stylesheet now sets the panel's, padding included, and should look at the result once. - A site on a host with no command line can take its next release from the admin, once it runs this one. This release itself still has to reach the site the old way. After that: build with
npm run build:dist -- --client=<slug> --zip, open CoreX → Releases, send the zip, read the statement, save the recovery link, type the version.docs/en/05-deployment/first-installation-by-hand.md, "A site that is already live", is the move step by step. It has not been run on a shared host: make the first one a release that changes nothing important. - A site that sets
DISALLOW_FILE_MODSadds one line towp-config.phpto use it:define( 'COREX_ALLOW_RELEASE_INSTALL', true );. Without it the screen reads and receives packages and refuses to install, as before, and says which line is missing. A package is not signed: with the line in place, whoever holdscorex_manage_releasescan replace the site's code. The ability can be denied to a role on Access & Abilities. - A client that adds a third-party plugin to its package after the build names it to the builder instead:
--with-plugin=<folder>. A plugin copied intodist/afterwards is in no release path, so an installation from the admin neither unpacks nor replaces it. - A package has to be named
corex-release-….zip, as--zipnames it, to be kept by the screen. A zip a client names itself is refused for its name. - Installing replaces each folder of the release whole. A file somebody changed or added on the host inside a CoreX plugin, an add-on, the site's own plugin or theme,
wp-content/vendororwp-content/packagesis replaced. Uploads,wp-config.php,.htaccess, WordPress itself and any plugin or theme the package does not name are left as they are. - A plugin the running release carried and the next package does not is removed from the site with the previous release, and WordPress switches it off. The screen names it first. The first installation from the admin on a site that was only ever unpacked by hand reads what the running release owned from
corex-release.jsonin the site's root. - A site whose plugins or themes are not in
wp-content/pluginsandwp-content/themes(a moved or renamed content folder) cannot install from the admin: the screen says so. - The site is in WordPress's maintenance mode for the seconds between the swap and the end of the database step. If the finishing request never comes, WordPress stops holding visitors off by itself after ten minutes, the schema is brought up to date by the first admin page or cron run as before, and the Releases screen finishes the installation when it is next opened.
- New in
wp-content/:mu-plugins/corex-release-recovery.php, written just before a release's folders are moved and kept while there is a release to go back to;corex-releases/holds the previous release's folders, which count against a host's file allowance until the next installation replaces them. Tables named…_cxrel_<minute>appear in the database and are removed 14 days later, the next time somebody opens Releases. - Going back does not undo a release's database changes. The previous release then runs on the tables as the newer one left them, and records its own schema version again on its first admin page, which adds what is missing and removes nothing. A release whose database changes the release before cannot run on has to say so in its notes. Going back removes the newer release from the site: to install it again, send its package again.
- A package left in
wp-content/corex-releases/incoming/for more than 7 days without being installed is removed the next time Releases is opened, whether it was uploaded or put there by hand. Keep your own copy. - On a network the table copies are of the main site's tables only. Every site's database is still brought to the release.
- Code of your own that built
ReleaseDeskor calledReleaseHostFacts::ofThisHost():ReleaseDesktakes a function that answers the host's facts in place of the list of folders;ofThisHost()takes two more arguments, both optional.ReleasesControllertakes two more collaborators. Nothing outside CoreX is known to build either. - On a production site with no delivery provider or with live delivery off, Send reply is now switched off, with the reason beside it. Before, the reply was accepted and refused without a word. To deliver replies: CoreX → Settings → Mail, Delivery provider: WordPress wp_mail, and tick Enable live delivery.
Corex\Mail\SubmissionEmailGatewayhas a new method,readiness(): ReplyReadiness. A site that implements this seam itself has to add it. A site that uses CoreX's email add-on, or none, has nothing to do.Corex\Mail\SubmissionEmailGatewayhas a second new method,preview(string $recipient, string $subject, string $htmlBody): array, answering['html' => …, 'text' => …]. A site that implements this seam itself has to add it, as it doesreadiness().- New route:
POST corex/v1/submissions/{id}/reply/preview, takingsubjectandbodyas the reply route does, guarded as it is, and answering{ preview: { subject, html, text } }. It sends nothing and keeps nothing of the reply. - The notification about a site's email design that failed now reads "CoreX used its standard design for the email instead", where it said the email was sent: a design can fail while a reply is only being previewed.
POST corex/v1/submissions/{id}/replyanswers 422 where a reply would be refused, with the reason, and sends nothing. It answered 200 with a result whose state wasrejected. Its answer gainsrecorded, false when the email went and the reply could not be kept.- A submission's detail (
GET corex/v1/submissions/{id}) gainsrepliesandreply_delivery. Replies are stored as post metacorex_submission_replies, one row to a reply. Code of your own that copies or removes a submission's personal data has to include it. - A reply's email looks different. It was the site's name or logo over a line in the theme's
primarycolour; it is now the standard design in CoreX's colours. To put a site's own colour back, set Email accent colour in CoreX → Settings → Mail; for more than the accent, or a template of the site's own, see the guide "A site's own email design". - New for a client site, and nothing it has to do: the filters
corex_email_brandandcorex_email_design, the commandwp corex make:email-design, and three settings (mail.brand.accent,mail.brand.logo,mail.brand.footer). A site that sets none of them sends the standard design. Corex\Email\Design\SiteEmailBrandtakes aConfigInterface. Code that takes it from the container is unaffected.Corex\Email\Message\EmailMessagehas a new last argument,plainText, and a message that carries one is sent asmultipart/alternative. Code that builds a message is unaffected. A mail driver of your own receives the field and may ignore it.Corex\Guides\Support\SupportEmailPaletteis removed. Its values areCorex\Email\Design\EmailPalette::light()and the constants ofEmailBrand.EmailStudioSubmissionGatewaytakes aReplyComposerwhere it took aLayout. Code that takes the gateway from the container is unaffected.- The admin bundle changed: rebuild
plugins/corex-configafter merging. - A person who is not an administrator loses the personal columns in the Data export until given "Export personal data" on CoreX → Access & Abilities. That is anybody with a role you gave "Manage data" or "Manage submissions". They still export the other columns. Decide who on the site should hold the right, and give it, when the site takes this release.
- A table your site registers with
ManagedTablecounts as personal data in every column until it passespersonalData. Nothing breaks: an administrator exports it as before, after a confirmation that now says "data nobody has classified yet", and anybody else is offered none of its columns. Declare each column to end that; a column that holds nothing about a person isDataField::PERSONAL_NONE. - Code of your own that read
personal_data_classfrom the Data catalog meets a new value,undeclared, and form submissions' answers arecontentorcontactwhere some werenone. - The Data catalog (
GET corex/v1/data/sources) answerspermissions.personal_datafor each source. Nothing is removed from it. - Data export files older than 30 days are removed the first time the daily clean-up runs after a site takes this release, and every later one 30 days after it was made. Their entries stay in the history. Download anything older that is still wanted before updating.
- The Data catalog (
GET corex/v1/data/sources) answerscategoryfor each source, and a Data export run answersparts,file_bytes,removed_reason,removed_byandremoved_at.source_keyandcolumnsare still answered, and are the first model's in an export of several. - New routes under
corex/v1/data-exports, guarded as the Data routes are. Nothing undercorex/v1/data/{source}/exportsis removed; its list now also holds an export of several models that covered that source, and its download refuses a file that was deleted or is past its 30 days. - Code of your own that implements
DataExportStorehas three more methods to implement:leaveOut(),removeFile()andholdingFilesBefore().DataExportAbout::facts()andDataExportFiles::collect()andfinish()take a run's models where they took one source's fields. - The admin bundle and two stylesheets changed: rebuild
plugins/corex-configafter merging.