Kenii Releases

October 2026

3.18.5

Every icon in the admin now comes from one set, in one weight. Toast messages no longer cover the Save button. The Calendar buttons are easier to tap. Seeding a demo site a second time works.

  • Changed Every icon in the admin is now from the Phosphor set, in one weight. The action pill on the curriculum screens comes from the Kenii component library.
  • Fixed Toast messages appear below the admin bar and the sticky editor header, so they never cover the Save button.
  • Fixed The Create and Attach buttons on the Calendar screen are tall enough to tap, and the old and new values in the audit log can be reached and scrolled with the keyboard.
  • Fixed Resetting demo data removes records in the right order, so seeding again no longer stops on a duplicate.

3.18.4

The Show me how tour now closes from Got it, Skip and the close button. A data health note left by a record that was purged outside Kenii now clears when you recheck it.

  • Fixed The Show me how tour on a program, course or credential closes from Got it, Skip and the close button. Before, none of the three did anything.
  • Fixed A data health note left by a record that was purged outside Kenii no longer stays forever. Recheck clears it when the record is gone from WordPress and holds no live row in the tables.

3.18.3

The printed catalog builds in pieces, so a large catalog fits in the memory a web request has. Site search finds pages and posts again when Curriculum is on. An academic calendar PDF can be attached to a year before its catalog year exists.

  • Added Each academic year on the Calendar screen has an Attach academic calendar button. The PDF shows as the download button on the Academic Calendar page even when the year has no catalog year yet. A PDF attached to a catalog year keeps working and takes priority.
  • Fixed The printed catalog PDF is built one program or credential at a time. A catalog of about 640 pages used about 750 MB at once and could fail on a host with less. It now peaks near 250 MB and prints the same pages and text.
  • Fixed Site search no longer drops pages and posts when the Curriculum module is on. Meetings that are not public stay out of search results.

3.18.2

The printed catalog now shows each credential’s requirements, the blank pages after the Programs and Credentials title pages are gone, and the sections are numbered in the order they print. New demo sites also come with a general education requirement list, a Team member, and programs and courses on both campuses.

  • Added The demo data includes a general education core as a shared requirement list, one Team member, and courses offered at the campuses of the programs that require them.
  • Fixed The printed catalog PDF shows each credential's requirements, the same ones its page shows, instead of only the title and total credits. The catalog PDF rebuilds once after the update.
  • Fixed The printed catalog no longer has a blank page after the Programs and Credentials title pages.
  • Fixed The sections of the printed catalog are numbered in the order they print. Before, a section could be labeled Four and print ahead of the one labeled Three.
  • Fixed A demo site's campuses count their programs and courses. The demo data stored the campus of each program in a form the Campuses screen could not read.
Show 10 earlier releases from October 2026

3.18.1

The Interest tab on the Demand screen now shows what Compass users were aiming for and how fast they want to go, next to the field table, and no longer shows two empty seat columns when nothing is scheduled. Every module’s sidebar has an icon on each top-level item.

  • Added The Interest tab shows the goal and pace counts beside the field table, with the same labels Compass shows and the college's own name for dual enrollment.
  • Added Every top-level item in each module's sidebar has an icon, and its children stay plain under a tree line.
  • Changed The Seats open and Gap columns on the Interest tab show only when at least one row has a real seat count. Without one, a line says seat counts appear once classes are on the schedule.
  • Changed A line under the Interest table says what "vs last year" compares.

3.18.0

Policies and handbook sections can now be saved with the data tables as the main copy and WordPress rewritten from them, behind a switch that is off everywhere and turned on nowhere in this release. Nothing changes on a site until someone turns it on, and every old table keeps receiving rows so a rollback loses nothing.

  • Added Every policy write goes through one save path. It is proved identical to the old writers on stored data, so a screen, an import and a command line save a policy the same way.
  • Added `wp kenii authority policy` reports, turns on or turns off the write switch for policies. Turned on, a save is planned and checked in the tables first, then WordPress is rewritten from them. It refuses to turn on while any condition fails and names the one that does.
  • Added `wp kenii shell heal –entity=policy` rewrites a policy post from its row in the tables. It does nothing without `–commit`, and says how many posts it compared.
  • Added `wp kenii store health` reports the policy switch, and whether the tables, the posts and the old meta agree, with counts.
  • Added `wp kenii rollback-prepare –entity=policy` returns offline and discontinued policies to a status the previous release can hold, turns the policy switch off and forgets the read verdict, so going back to the older release is safe.
  • Added The data tables can hold a policy that is offline or discontinued, and the source id and source address of a policy. Core schema version 36.
  • Added The old tables keep receiving the rows that now go to the data tables (the legacy echo), and `wp kenii legacy-echo` compares them and copies what was missed.
  • Changed Handbook sections and policies sort by position, then title, then id on every page and screen, so two policies at the same position never swap places.
  • Changed The admin library is Kenii UI 2.2.1. Icons inside library components change from Lucide to Phosphor, table cells line up under their column headers, and the neutral admin grounds come from the library.
  • Changed A proposal comment shows the time it was written in UTC, not in site time.
  • Fixed Cloning a handbook into a year that already holds the clone answers with a clear refusal instead of an error.
  • Fixed Reading the next id of a table on MySQL 8 no longer returns a stale value.
  • Fixed The board meeting types and the redirect list are held as rows in the data tables, and a mirror that stops part way reports what it refused.

3.17.0

Kenii’s own data tables now hold everything a college entered that WordPress used to keep in loose places: ten separate tables, and year lifecycle, year files, proposal comments, watchlists, the default workflow, alerts and validation rules. A taken-down year can no longer read as public from the tables. Pages look the same, with one exception: a course page now lists every credential that requires the course.

  • Added The data tables now hold notifications, stage durations, the document index, handbook sections, the audit log, signals, group places and the credential and course index, each copied from the old table with its ids kept. The old tables stay in place, untouched and no longer written, so nothing is lost.
  • Added Votes are split into one table for Curriculum ballots and one for Board ballots, each linked to the thing voted on and the person who voted.
  • Added Catalog years, year files (catalog PDF, calendar PDF, Word and archive), proposal comments, the proposal watchlist, the default workflow, the alert, the announcement and the curriculum validation rules each have rows in the data tables, kept in step with WordPress and checked by a compare command.
  • Added `wp kenii schema install` and `wp kenii store health` name every step that could not run or refused a record, and exit with an error while one stands.
  • Changed A course page lists every credential whose requirements contain the course. Before, it read an index that could miss a credential added or edited later.
  • Changed A year's row follows its lifecycle: draft, active, published or offline. Before, an offline year and the active year both read as published in the data tables.
  • Fixed A year taken offline no longer reads as published in the data tables, a year that had no row now gets one, and a deleted year that still has records stays hidden.
  • Fixed A person who has cast a recorded ballot cannot be deleted. The admin sees a clear reason before anything is written to the audit log.
  • Fixed Deleting all signals no longer removes the Compass exploration history.
  • Fixed The audit log keeps the account id of a user who was later deleted.

3.16.0

A college whose renewal has lapsed now keeps its catalog pages live and read-only, with no time limit, and can still export everything it entered. Every other module stops and nothing is deleted. A license that was withdrawn is handled differently and serves nothing.

  • Added A site whose renewal grace has run out keeps serving its catalog pages, read-only, for as long as it likes. Programs, credentials, courses, policies and every other catalog record type stay readable. Every other module stops, and every write is refused in one place with a plain message.
  • Added The admin of a lapsed site says its license has ended and offers export only, instead of editing screens that would answer with an error.
  • Changed The data export now covers everything a college entered, not three record types, so a college that lets its license end can take all of its work with it.
  • Changed A license that ended is now told apart from one that never existed, using only fields inside the signed answer from the licensing service. A withdrawn license keeps nothing live.
  • Fixed The board record types now register whatever the license says, and only their screens and routes are gated. Before, a lapsed site had no board records to read or export, and they would not have been there on renewal.

3.15.4

Tighter control over who can change and see catalog records, an internal program that now opens from its own link, and saves that refuse a bad value instead of quietly storing a different one.

  • Changed A program set to internal now opens from its own link. It stays out of the public program list, carries a "do not index" tag, and is left out of the sitemap, so it is unlisted rather than hidden. Before, its page returned "not found".
  • Fixed The stock WordPress Editor, Author and Contributor roles no longer give any right to create, edit or delete catalog records. Those rights now come only from the Kenii access levels. Before, the catalog's record types could be changed through WordPress's own routes by anyone holding those roles.
  • Fixed A board document can now only take in a file the person attaching it uploaded. Before, any attachment on the site could be named, pulled into the document and read through its download link.
  • Fixed The breadcrumb route no longer returns the title of an unpublished or non-catalog post, or the list of installed plugins. The data export refuses a catalog year the public cannot see unless the caller manages the catalog, and refuses a year name that does not exist.
  • Fixed Three Pathways routes and the compass comparison no longer return unpublished records, and the compass visit limit no longer trusts an address sent in a request header.
  • Fixed Saving a record now refuses a value the data table cannot hold as written: a missing value for a required field, a number outside its range, text in a number field, or an unreadable date. Before, each was stored as an empty value, a clipped number, zero or a zero date without a warning. Every existing record on the live college site was checked against the new rules first, and none would be refused.

3.15.3

A batch of fixes for places where deleting, restoring or linking records left WordPress and Kenii’s data tables disagreeing, and one check that could not report a closed switch. Nothing is added; each of these is something that went wrong quietly before.

  • Fixed Deleting a catalog year that a curriculum proposal, a course import or an addendum still names is now refused before anything is removed, and the message says what still uses it. Before, the year left WordPress first and then failed on its stored row.
  • Fixed A program can only build on a program in its own catalog year. A link across years used to be accepted and then refused by the data tables, which made the yearly catalog clone drop links.
  • Fixed A requirement entry that carries a course but no course code now takes the code from that course. Before, it was dropped, and that closed credential reads from the tables for the whole site.
  • Fixed Restoring a campus, a term, or any other record from the trash now clears its deleted mark in the data tables.
  • Fixed The trash list now shows campuses, terms, addenda and transfer agreements, and records from every catalog year, so each can be found and restored.
  • Fixed Deleting the last program that shows a typed contact now removes the hidden contact record it created.
  • Fixed `wp kenii schema install` now fails when a read switch that was open closes after a code update. Before, it always reported success.

3.15.2

The groundwork for letting a site make its own record numbers is now tested end to end, and the data layer has a test that any storage engine must pass. Nothing a visitor or an editor sees changes.

  • Changed **Kenii can now be tested with a site making its own record numbers for 35 kinds of record.** A new check creates, reads, changes, trashes, restores and deletes each kind both ways and compares the two. Sites keep their current WordPress numbers; the setting stays off.
  • Changed **Looking up a contact or role that has no record answers "none" instead of hiding a real storage error.** Only a missing entry in the identity map is treated as "none" now.
  • Changed **A shared storage test now runs against the WordPress store and a standalone SQLite store.** It records four places where the WordPress store would silently shorten or zero a bad value, so they can be fixed in a later release.

3.15.1

Curriculum proposals now submit, approve and create records correctly on sites that read from the data tables, and credential requirement edits reach the tables during the save instead of at the end of the request.

  • Fixed **Submitting, voting on, approving and materializing a curriculum proposal gave wrong results once proposals read from the data tables.** While a change was being saved, the proposal was read back from the tables before they had caught up, so a submitted proposal reported itself as a draft, an approval did not advance the step, and an approved proposal created a course with no fields. Reads made during a save now come from WordPress, which is always current. The proposal check that decides whether tables may be read now also catches this, and a new check walks a proposal from creation to materialization.
  • Fixed **Credential requirement edits reached the data tables only at the end of the request.** On sites reading credentials from the tables, saving a credential rebuilt its requirements from the very table it was about to update, so the table caught up only in the end-of-request repair. It now rebuilds from WordPress during the save.

3.15.0

Kenii now keeps its own number for every record, alongside the WordPress number, so the same catalog can later run on other platforms. Nothing a visitor or an editor sees changes.

  • Added **Kenii's own record numbers.** Every program, credential, course, policy, person and other record now has an entry in Kenii's identity map, which links Kenii's number for the record to the WordPress number. Existing records keep the numbers they have today. This is the groundwork for running the same catalog engine on platforms other than WordPress.
  • Added **`wp kenii identity check`** reports any record missing from the map, any map entry whose WordPress object is gone, and contact records at risk of being overwritten. The result also appears in `wp kenii store health`. **`wp kenii identity adopt`** repairs missing entries, and **`wp kenii identity renumber-hidden`** moves at-risk contact records to safe numbers (both preview by default).
  • Changed **Admin lists and public catalog pages make fewer database lookups** when reading from the data tables.
  • Changed Data schema version 24. The id range is raised by `wp kenii schema install`, never during a page load.
  • Changed A new rail creates one of every kind of record with Kenii-made numbers and proves every page, PDF and comparison still matches.
  • Changed The REST rail now always runs with the PHP rails, since several of them read the records it creates.
  • Fixed **A contact created for a program or course could be overwritten by a later person record that received the same number.** New contact records now get numbers from a separate range that WordPress never reaches.

3.14.4

The plugin package no longer carries internal documentation files, and updating removes them from the site.

  • Security **Internal documentation is no longer packaged with the plugin.** Earlier packages included internal notes and implementation documents in folders the plugin never reads. Files inside the plugin folder can be opened from the web, so they could be reached on a site running the plugin. Updating to this version removes them. Nothing a visitor or an editor uses has changed.
  • Security The release now ships only the plugin's runtime folders and files, and fails if anything else, or any note file inside a runtime folder, is in the package.

September 2026

3.14.3

Where a site reads from the data tables, the screens that ask which credentials use a course now ask the tables too, so a course is never shown as unused when a credential lists it.

  • Fixed **Course usage no longer misses credentials.** The older course-to-credential index is rebuilt only when a credential's own requirements are saved, and it reads plan-of-study courses by saved id only. So a course created after the credential, or a plan entry that names a course by its code, was left out. On sites reading from the data tables, the course usage panel, the curriculum impact check on a course proposal, the unused-course check in the signals report, the credential list on a course page and the Reports ripple block now read the tables, which have every link. Each credential is counted once, and one that lists the course in its plan of study is shown as a plan course, as before. On Martin this restores 11 links, including CHM-130, CHM-130A and EDU-131A on the nursing, arts and science degrees.
  • Fixed CI runs only the suites a pull request's changed files can affect (`.github/scripts/pick-suites.sh`), and the full suite runs once when a release is tagged; the release cannot build or publish unless it passes. The separate run on `main` after a merge is gone.
  • Fixed The public test fixture now has a program type, a plan of study that names a course by code alone, and is written to the data tables, so table-backed rails have real links to check.
  • Fixed New rail `tests/course-usage-from-tables.php` hides a real table link from the legacy index and proves the table readers still find it, and that a course a credential lists several times still gives one row per credential. It puts the index back afterward. The WordPress side of the course, program and Pathways page checks still reads the legacy index on purpose, so those checks stay able to see a difference.

3.14.2

A catalog PDF made by an older engine is now rebuilt until a build succeeds, and a queued rebuild can never overwrite a PDF the college uploaded.

  • Fixed **An old catalog PDF can no longer stay public after an update.** Each generated catalog records the engine that built it, and only a successful build writes that record. Once an hour Kenii queues a rebuild for any generated catalog made by an older engine, so a rebuild that fails is retried rather than forgotten.
  • Fixed **A rebuild never overwrites an uploaded catalog PDF.** The build now checks for an uploaded PDF when it runs, not only when it is queued, so a PDF uploaded while a rebuild was waiting stays in place.
  • Fixed **A build that keeps failing stops after three tries** instead of retrying every hour, and the reason is kept on the catalog year's PDF status for an admin to see. A later successful build clears it.
  • Fixed New rail `tests/pdf-rebuild-heals.php` builds the active catalog, then proves a stale file is queued, a real failed build is counted and retried up to three times, an uploaded PDF is refused and left byte-for-byte untouched, a current file and a year with no generated PDF are left alone, and the check waits an hour. It restores every record and file it touches, and goes red when the success record, the upload guard or the retry cap is removed.

3.14.1

A license now counts only if Connect signed it for this site, and a paid license keeps working through the end of its term even when Connect cannot be reached.

  • Changed **A paid license holds for its whole term, even with Connect down.** When Connect includes the end of the paid term, the license keeps working until that date plus 30 days without a check. A complimentary license with no end date does not lapse offline at all. Only a signed answer from Connect turns modules off mid-term.
  • Changed A site updating from an earlier version checks in with Connect right away so its saved license is replaced with a signed one.
  • Changed The entitlement rail signs real grants with a throwaway key and covers tampered, forged, wrong-site, mid-term, comp and lapsed licenses, on test and live hosts. It goes red with the signature or term check removed.
  • Changed The credential PDF compare now reads every catalog year, not only the active one, and CI fails when a maintenance query does not say which years it reads.
  • Changed The offline grace period is read from `Kenii_License::OFFLINE_GRACE_DAYS` instead of a second copy of the number.
  • Fixed **A license must carry Connect's signature to count.** Kenii stores each signed license whole and checks the signature every time it reads it, so a license written straight into the database, or copied from another college's site, does nothing.
  • Fixed **The module override no longer applies on a college's live site.** `KENII_ENTITLEMENT_OVERRIDE` in wp-config is honored only on localhost, `.local`, `.test` and `.localhost` addresses and on kenii.app with its subdomains.

3.14.0

Every PDF, Word file and public page Kenii makes is now checked for accessibility on every build, and the checks found and fixed real problems students were meeting on live pages.

  • Changed **Kenii now needs PHP 8.2 or later**, for the accessible PDF engine. The old PDF library (Dompdf) is removed. On an older PHP the plugin turns itself off with a notice instead of stopping the site.
  • Changed Building a PDF asks WordPress for up to 768 MB of memory, since a full catalog peaks near 450 MB.
  • Changed PDFs are set in the fonts they always actually used (DejaVu Serif body text), now embedded in every file. Spacing is close to before; labels are slightly bolder.
  • Changed The saved link color is fitted to pass contrast in the same way as the brand color.
  • Changed `tests/public-a11y.mjs` loads every public page type and checks the whole page against WCAG 2.0, 2.1 and 2.2 A and AA plus heading and landmark rules, failing only on Kenii markup. `tests/a11y-surfaces.json` lists every public shortcode and block, and `tests/a11y-coverage.php` fails the build when one is missing.
  • Changed `tests/pdf-ua.sh` validates every kind of PDF with veraPDF against PDF/UA-1, plus a document built from pasted word processor markup. `tests/docx-a11y.php` checks the Word export. `tests/brand-contrast-sweep.mjs` runs six real school colors in CI. Both page checks count the Pathways `kp-` markup as Kenii's.
  • Changed The admin screen check now includes the WCAG 2.1 rules it was skipping.
  • Fixed **Every PDF is a tagged, accessible document.** The catalog, credential, Reports, curriculum report and proposal evidence PDFs were untagged, so a screen reader got a stream of text with no headings, lists or table structure. All five now come from one engine in PDF/UA-1 mode, with the document language, title, heading structure, table headers and image text a screen reader needs.
  • Fixed **The catalog PDF now shows its running header and page numbers.** The old engine skipped the script that drew them, so every page went out without either.
  • Fixed **Text pasted from a word processor no longer stops a PDF.** Fonts such as Calibri or Arial are set in the nearest font Kenii ships, and an image with no alternative text is left out of the PDF instead of being tagged with made-up text.
  • Fixed **Generated PDFs rebuild once after this update**, so no catalog or credential PDF made by the old engine stays public.
  • Fixed **Hero text and card counts stay readable on a light brand color.** Hero titles use the computed text-on-brand color, community card counts are fitted to their community color, and brand-colored text on a light brand tint is fitted to that tint. Colors that already passed are unchanged.
  • Fixed **Career outcomes and transfer sections use the right heading level**, one level under the page title, with a heading level setting for other placements.
  • Fixed **Report PDF tables size their columns to what they hold.** A long text column no longer wraps one word per line beside a number column, and number headers no longer run into each other.
  • Fixed **Program and career community cards use the right heading level.** Card titles sat two levels under the page title; they now sit one level under it, and the Program Grid and Career Communities blocks take a heading level for other placements.
  • Fixed **Headings a college types now fit the page.** A college picks heading levels by how they look, so a program description under a level two heading often started at level four, and pages and documents skipped levels. Headings in college text are now fitted under their real parent everywhere Kenii prints that text, and in every catalog document. A section with no title of its own no longer drops its groups a level too far.
  • Fixed **Brand colors can no longer break contrast.** Text on the brand color and the brand color as text are now worked out from whatever color the college picks. A color that already passes looks exactly as it did; a light color such as gold gets dark button text and a darker shade for text.
  • Fixed **The hero no longer covers the page title on other themes.** It slid up a fixed 130 pixels, sized for one college's see-through header. That is now the setting "Space under a see-through site header". Sites that already ran Kenii keep 130, so nothing moves; new sites start at 0.
  • Fixed **Compass and the catalog wrapper no longer add a second main landmark** inside the page's own.
  • Fixed **The Word export declares its language and keeps all six heading levels**, instead of folding levels three to six into one.
  • Fixed Opening a board body's settings at an address with no body now explains where boards are made instead of loading forever.
Show 7 earlier releases from September 2026

3.13.0

A run of releases could stop the read check running at all, silently, which left a college reading its catalog the slow way with nothing on any screen to say so.

  • Added **`wp kenii store health` reports a recheck that gave up, one that ran and refused, and a queue that has been waiting too long.** It names each record type and the command that finishes it.
  • Added **The Data Health screen says when part of the catalog is reading the slow way**, naming the record types still waiting, the ones the recheck gave up on, and the ones that ran and found a difference, instead of a single green state that hid all three.
  • Added **The screen says how long the oldest unresolved problem has been waiting**, and how many problems a later save already put right, so a long list of old history no longer reads as a pile of failures.
  • Added `ReadGate::regate_summary()` reports the recheck queue in one shape for a screen or the command line: what is pending, what gave up, what ran and refused, what passed, how old the queue is, and whether it needs a person.
  • Added A give up is carried by a flag on the result rather than by the wording of its refusal. Searching the message text broke under translation, and matched an unrelated data refusal that happened to use the same words.
  • Added `tests/regate-attempts.php` is a new rail, wired into CI. It proves its own subject on a site that has never recorded a verdict, rather than stopping. Every defect above has a check that goes red on its own when that fix is removed, and eight go red when the attempt count is carried forward again. It runs 31 checks on a site with real data and 30 on a bare one, where only one record type is eligible for a recheck.
  • Added `tests/settled-prune.php` is a new rail, 14 checks, wired into CI. Five go red when the trim is removed.
  • Added The CI rail step runs every rail even when one fails, and names every failure. One rail that stopped used to end the step and skip every rail behind it.
  • Added `tests/store-recheck.php` records the note list before it creates its own test credential, so the rail no longer leaves a note behind about a record it deleted. `tests/read-gate-check.sh` measures its note baseline against the trimmed log.
  • Fixed **A new release now gives every record type a fresh chance to prove itself.** When the plugin changes, Kenii re-checks that each kind of record reads correctly from its own tables before it reads from them. That check gives up after two failed background attempts, and the count was carried forward across releases, so several releases in a row pushed every record type past the limit and the check stopped running. The site kept reading from WordPress with nothing to say so. A new build now clears the count and the old refusal, and a site that had already given up is healed by the upgrade itself with nobody typing a command.
  • Fixed **A second build of the same version counts as a new build.** The queue used to be keyed on the sentence shown to a person, and two builds of one version produce the same sentence, so a site that shipped twice without a version bump was never healed. It is keyed on the build itself now. A person asking for a recheck also starts over however many times they ask.
  • Fixed **A check that ran and refused is reported.** Only the give-up case was counted, so a record type that ran its check, disagreed with the data and dropped back to reading from WordPress appeared in no list at all, and the screen could still say the data was healthy. That case is now named separately from a check that never ran, because it means something different.
  • Fixed **A record type someone fixed by hand stops asking for attention.** Nothing cleared the stored refusal except a new build, so the warning and the screen banner carried on forever after the person had done exactly what they were told.
  • Fixed **Rechecking one record type no longer silences the warning for the rest.** The queue clock was restarted by any request, so rechecking one thing reported sixteen others as freshly queued. Asking for a full recheck twice also reported the new queue as weeks old.
  • Fixed **A plan of study naming a course that does not exist in its catalog year is recorded again**, along with a choice inside one and a requirement group entry. All three used to be refused outright, and when that rule moved the condition stopped writing any note, so the link was dropped and nothing said so. The entry is stored unlinked and reported, because a person has to decide what the course should be.
  • Fixed **A cleared note no longer counts toward the refusal total.** A note that was already settled was still counted, so a screen could report a problem that had been fixed.
  • Fixed **Settled sync notes are trimmed to a recent window of 25.** A note is settled once a later save proved the record reaches the tables, so it is history rather than a problem. Without a window the settled entries outnumber the open ones many times over and the log reads as a pile of failures. The number removed is kept as a running total, so nothing is lost from the count, and a note that is open or stuck is never trimmed. Measured on a real college's note shape: 197 notes and 46,990 bytes down to 26 notes and 6,140 bytes, with the one open note still there.

3.12.1

A program renamed between catalog years reads as a rename rather than as a deletion and a new program.

  • Fixed Comparing two catalog years matched a program by its name, so a program renamed between the years showed up twice: once as removed and once as added. Now that a copy records which program it came from, the comparison follows that link and reports the rename, saying what the program used to be called. On real college data this turned ten removals into eight, and the eight left are the programs that really were dropped.
  • Fixed The comparison holds its answer for ten minutes and threw that away whenever someone saved a program. A repair run from the command line does not count as a save, so a college could run the repair and see the old answer, and conclude the repair had done nothing. The repair now clears it.

3.12.0

A report that says whether every course a credential requires actually exists in the catalog.

  • Added A Requirement Coverage section in the report builder. For each published credential in a catalog year it walks the requirements and checks that every course named has a course record in that same catalog year, that the record is published, and that it carries credit hours. A rule that accepts any course with a given prefix is checked for whether the year publishes any course with that prefix at all. Credentials with nothing to report are left out, and the section states plainly that it cannot say whether a course is scheduled to run in a term, because class dates are recorded for continuing education classes only.
  • Changed The report PDF is set for print rather than for a screen. Numeric columns are held narrow so a credential name has room instead of wrapping three times, lines are tighter, a row no longer breaks across a page, and the wording of a repeated finding is short enough to read down the column. One table that ran to two pages now fits on one.
  • Changed The cover carries a list of what is in the report, each section with the question it answers, so a reader knows what they are holding before they turn the page.
  • Fixed Every report cover printed "Covering to" with no dates. A scope that names a catalog year carries no start and end date, and the missing values were not read as empty, so the sentence ran with nothing in it. The same fault sat in two more places in the report code and all three are guarded now. A cover that names a catalog year says so.
  • Fixed Choosing a catalog year by its record rather than by its name quietly fell back to whichever year the site had set as active, so a report could be produced for the wrong year without saying so. Either way of naming the year now works.
  • Fixed A report PDF opened in the browser with no way to save it, which in Safari leaves a person stuck looking at a document they cannot keep. It now downloads. Adding view=1 to the address still opens it in the browser for anyone who wants that.
  • Fixed The prerequisites section read "A or B or C" as three separate missing courses and listed all three against every credential holding that course, which buried the real findings under one fact repeated forty nine times. It now reads a choice as a choice, reports it once, and says nothing at all when one of the options is already required. That last part removed a genuine false positive.

3.11.1

A program renamed between catalog years can be joined to its earlier self once a person confirms it is the same program.

  • Added `wp kenii repair program-lineage –confirm=` joins a copy whose title no longer matches the program it was copied from, for the records named and no others. The refusal stays the rule, because a new name can mean a new program, and the run says out loud which joins happened on a person's say so. Refusals that are not a judgment call, such as an original that is gone, still refuse however they are named.

3.11.0

Four repairs for things that had no way to fix themselves: a note that outlived its problem, a calendar missing from the catalog PDF, programs never joined to the ones they were copied from, and a column the design had dropped.

  • Added `wp kenii store recheck` now looks at notes saying the tables refused a record, not only at writes that failed. It clears one only when the table really holds the row, nothing would refuse it today, and it matches WordPress. Anything still refused is named with the reason.
  • Added `wp kenii repair term-years` gives a term the catalog year it names, and fills in the written year from the tag when that is the half missing. It refuses when the two disagree, when a term carries neither, when more than one year is tagged, and when no catalog year of that name exists yet.
  • Added `wp kenii repair program-lineage` joins a program copied into a new catalog year to the one it was copied from, matching on both the name in the address bar and the title. It refuses a rename, a missing original, a copy named for the wrong year, a line already taken in that year, and anything circular. Two programs sharing a name where neither is a copy are reported for a person to judge.
  • Added `wp kenii schema retire-columns` removes a column the design has dropped, and only when asked. It refuses while the column is part of any index or key, while the move it belongs to is unfinished on that site, and while any record is marked there and nowhere else, saying how many.
  • Fixed A term carried its academic year in two places and the editor only ever set one of them, so a year's calendar could show on the site and be missing from the catalog PDF. Saving a term now works the tag out from the year it names, and refuses a year that does not exist instead of quietly creating a catalog year by that name.
  • Fixed The check that decides whether a kind of record may read from the tables counted refusals that had already been dealt with. A cleared note could be dropped to make room and a new refusal would then not raise the count, leaving reads open on a record that shows on neither the page nor the admin list. It now counts only refusals nobody has dealt with.
  • Fixed Making room in the list of notes had stopped protecting writes that never reached the tables, and notes that only record what happened could trip the shutdown that closes reads across a whole site. Room is now given up in order, oldest settled history first.
  • Fixed A refusal that came back after being cleared was swallowed by its own cleared ancestor, so a problem returning went unrecorded.
  • Fixed A note written while a recheck was running was overwritten when the recheck saved its own older copy of the list.

3.10.1

A staff directory sync could pull in every student at the college, and a settings switch that never did anything is gone.

  • Removed The display section carried a switch called "Show attribute badges" that nothing in the plugin ever read, so turning it on or off changed nothing on any page. It sat directly above the switch that really does control the attribute letters, where it could only mislead. It is gone, and the switch that works keeps its own name, "Attribute letters in course tables".
  • Fixed The staff directory sync asked Microsoft Entra for every enabled member of the tenant, and at a college students are members. The one narrowing setting defaulted to empty and an empty setting returned everybody, so a college that had not filled it in could pull its whole student body into the staff directory. That setting is now required before a sync will run, an empty one returns nobody, and a department name works as well as an organizational unit so a cloud-only college can use it. Synced people arrive as drafts, so putting someone on the public directory is a decision a person makes rather than something a nightly job does for them.

3.10.0

A college keeps its own list of course attributes and shows their letters beside a course, and the word “Track” can be renamed to whatever the college calls it.

  • Added Course attributes. A college writes its own list, each one with letters, a name and a description. The letters show beside a course in requirement lists, plans of study, program course lists, the course directory and both PDFs, with a key underneath that spells each one out. The course page lists every attribute by name. A screen reader hears the full name, never the letters on their own.
  • Added The transfer switch each course used to carry is now one of those attributes. Kenii moves it once per site, so a course marked as transfer approved keeps that mark under the new name.
  • Added A setting keeps the letters out of the tables and lists until a college asks for them. The course page still names the attributes either way.
  • Added The "Track" label is now a setting. A college that calls it an Option, a Concentration or anything else sets the word once, and the program page, the program editor, its tour and the lint findings all follow. A per-section override still wins over it.
  • Fixed The first page load after a plugin or schema version change ended in a fatal error, because the read gate asked WordPress to run cron earlier than WordPress allows. The load now finishes, and the check it wanted still runs on the next cron tick.