2.3.0

The largest release so far, and really two things at once. **Curriculum arrives as a full module**: propose, review, approve and record course changes, with approval workflows the college configures, committee voting with quorum and thresholds, meeting agendas, a field-by-field redline of what is changing, and a printable evidence package for accreditation. Nothing a committee is still debating touches the live catalog until an approval deliberately writes it into a catalog year. **Credential requirements also gained real degree-audit structure** , groups with rules like “take all”, “choose one”, or “take 49 credits from these subjects, maximum 12 per subject”, which is one rule where it used to take hundreds of rows. Existing requirements keep working untouched. Colleges can also now manage their own lists , program areas, credential types, delivery modes, transfer institutions , without leaving Kenii for the WordPress admin.

  • Added Requirement groups: credential requirements now express real degree-audit structure. A section can hold multiple groups, each with its own rule (take all, choose one, take N credits, take N courses, or complete N of the child groups), and sections can be grouped under headings with auto-summed credit subtotals. A group's members are either an explicit course list or a subject rule , a set of course prefixes with optional level exclusions, a maximum-credits-per-subject cap, and a note , so "take 49 credits from these subjects, max 12 per subject" is one rule instead of hundreds of rows. Legacy flat requirements parse forever and render unchanged; a credential upgrades to the new shape the first time it is saved. One shared traversal layer (`includes/requirements.php` and its JS counterpart) serves every consumer , public rendering, junction-table reverse lookups (explicit courses only; subject rules deliberately never inflate "programs using this course"), Compass, exports, year diffs, and editor counts. On the public page a subject rule renders as a sentence with each prefix linked into the prefix-filtered course directory. The credential editor gains the group/heading structure with progressive disclosure: a plain course list looks and works exactly as before.
  • Added `FormSection` shared primitive: titled card sections with an optional collapsible "Advanced" mode, used to restructure the program, credential, and course editors , identity fields first, everyday fields next, rarely-touched settings collapsed. Layout and hierarchy only; no field was removed, renamed, or moved to different storage.
  • Added In-app management for the remaining customer-defined catalog taxonomies, so every list a college maintains stays inside Kenii with no trip to raw WordPress. A new "Manage Lists" hub (in the Catalog sidebar) links to standardized management screens , matching the Program Areas pattern (page shell, shared row actions, add/inline-rename/guarded-delete) , for credential types, program types, delivery modes, course prefixes, and transfer institutions. The generalized admin REST controller now serves all of them behind the same in-use delete guard (refuses with the assigned-record count rather than orphaning) and duplicate-name rejection, decoding entities on intake and read and audit-logging every create, rename, and delete. Credential-type usage is counted across both programs and credentials. Structural values that the catalog's logic depends on are guarded: the program types "Curriculum" and "Continuing Education" (which drive the curriculum vs continuing-education rendering split) cannot be renamed or deleted, and the default "In Person" delivery mode cannot be deleted (its display label may still be changed, since the default is keyed by slug). Course prefixes are derived from course codes, so free-create is intentionally not offered; existing prefixes can be renamed for display (the slug is preserved so derivation keeps matching) and deleted only when unused. Transfer institutions gain rename and guarded delete to complete their in-app management. The fixed two-value `course_type` system vocabulary is deliberately excluded, as its values drive course rendering and are not a customer-editable list.
  • Added In-app program-area management. Program areas can now be created, renamed, re-parented, and deleted entirely inside Kenii , no trip to the WordPress taxonomy screen. A gated admin REST controller (`kenii/v1/admin/program-areas`) lists areas with the count of programs using each, creates them (rejecting duplicate names), renames/re-parents them, and deletes them behind an in-use guard that refuses with the assigned-program count rather than orphaning programs. A "Program Areas" management view (reached from a "Manage Areas" action on the Programs list) presents the areas in a table with add, inline rename, and guarded delete. The program editor's area field gains an inline "+ Create new area" affordance that adds and selects a new area without leaving the flow. The controller is structured to generalize to the other Catalog taxonomies via an allowlist; only `program_area` is wired in this release.
  • Added Curriculum module (license-gated): a complete curriculum-governance workflow for proposing, reviewing, approving, and recording course changes , new, revise, and discontinue proposals that stay isolated from the live catalog until an approval deliberately writes them into a catalog year. The module comprises:
  • Added Proposals , , draft course proposals (new, revise, discontinue) stored as an independent `kenii_proposal` entity that is isolated from catalog-year queries by construction. The proposal payload speaks the canonical field-bag vocabulary so the change-review redline reuses the existing diff engine. Gated REST endpoints and admin Proposals list + editor surfaces; the proposal data layer registers regardless of entitlement.
  • Added Institution-configured validation , , a validation engine driven entirely by rules the college configures for itself, stored in one site option. Admins set required fields per proposal type, a credit-hour range, credential total credit-hour envelopes, and an effective-dating check , each rule carrying a blocking or advisory severity. At submission a blocking violation aborts the proposal with the list of failed checks; advisory violations attach as non-blocking warnings. The engine is generic and ships no external or regulatory data; with no rules configured every submission passes. A gated Validation screen loads and saves the rule set, and the proposal editor surfaces blocking and advisory results distinctly.
  • Added Importable state packs , , an optional onboarding seed that pre-fills a reference course library and the institution's validation rules from a state's published values. A pack is imported once and then owned by the college , Kenii never runs it live, nothing auto-updates, and no pack asserts compliance; every pack-derived check is advisory by default and the active pack always shows its captured date honestly. The reference course library lives in its own isolated table, completely separate from the real catalog: a pack import can never create, modify, or delete a real course, program, or credential, and never changes a catalog-year count. Imports upsert and archive-flag reference courses (retired codes are archived, never deleted or reused). Three import paths , a bundled pack file, admin upload, and `wp kenii pack-import` (dry-run by default). A North Carolina pack ships bundled. A new optional `course_code_in_library` validation rule (advisory by default) flags proposal course codes absent from the active reference library. A gated State Pack screen shows the active pack, imports packs, and browses the reference library as a clearly labeled reference menu.
  • Added Workflow engine , , configurable approval workflows (`kenii_workflow`, structured step builder , steps, role/user assignees, participation modes, conditional step injection rules) with a seeded "Standard Curriculum Approval" template. Proposals submit into a route snapshot frozen at submission , later workflow edits never touch in-flight proposals , with approve/return/hold/resume decisions, an append-only decision log, and step/decision notifications.
  • Added Committee voting , , any approval step can be a committee vote with a quorum and threshold (simple majority, two-thirds, or unanimous). Members record approve/reject/abstain with optional notes into an append-only votes table , a changed vote supersedes the prior one, the tally uses each member's latest vote, and the full history is preserved. A step advances automatically only when quorum and threshold are met; when the threshold becomes mathematically unreachable no automatic action is taken and a member returns the proposal with a reason. The frozen route snapshot also freezes each step's committee configuration and membership.
  • Added Meetings and agendas , , track meetings (title, date, notes) and view a live agenda of every proposal currently awaiting a committee vote, each with its running tally and a link to the review screen; votes cast while a meeting is selected are recorded against that meeting for a minutes trail. A meeting with recorded votes cannot be deleted.
  • Added Review screen , , field-by-field redline of proposed vs current content (formatted rich text for descriptions), catalog impact via credential requirement/plan-of-study usage, discussion comments, and FYI notifications to affected colleagues , with the approve/return/hold/resume decision bar inline.
  • Added Substantive-change radar , , an advisory decision-support layer that computes signals on a proposal (credit-hour change vs the current record, a rough new-content magnitude, program-closure detection, delivery-method change) and flags where a configurable threshold may be crossed , it never asserts that a change is substantive. The threshold values are admin-editable configuration data (stored in the same validation-rules option, seedable via a state pack) carrying an honest "policy as of , verify against current policy" marker and no authority claim. When a signal crosses at submission the radar sets a `substantive_change` flag, which the workflow rule engine turns into an injected accreditation-liaison review step; setting the flag routes a human in and decides nothing, and the radar never changes proposal state or blocks submission on its own. The institution's determination is a human answer (yes / no / unsure + rationale) recorded on the proposal as accreditation evidence , Kenii never sets it automatically , with the latest answer kept and every change audit-logged. The review screen carries a radar panel with the signals, their thresholds, an advisory disclaimer, and the determination control.
  • Added Approval materializer , , final approval writes the change into the target catalog year through the catalog write services , new courses are created, revisions update the target year's counterpart course (matched by code), and discontinuations remove it. Draft and active years take record writes; published years receive an addendum documenting the change instead, so their records stay immutable; offline years are refused. Materialization runs at approval but never blocks it , a failure is recorded on the proposal with a Retry action, and re-running is idempotent. The review screen shows where the change landed (or the error), and the proposal keeps its full evidence trail.
  • Added Per-proposal evidence package , , a printable PDF that assembles a proposal's existing governance record , identification, the field-level change (redline), catalog impact, the approval history with its frozen route snapshot, committee votes and tallies, and the substantive-change signals with the institution's own determination , structured around the SACSCOC prospectus background-and-context outline. It is a factual record of what was proposed, who decided, how the committee voted, and what the institution determined; it asserts nothing on Kenii's behalf, shows the substantive-change answer as the institution's, and carries honest policy as-of dates with no compliance claim. Sections with no data read "none recorded". Any proposal state can export; the document is rendered through the existing catalog PDF pipeline, streamed from a gated endpoint, and every export is audit-logged. The review screen carries an "Export evidence package" action.
  • Added Reports and analytics , , six curriculum workflow reports with on-screen tables, HTML/CSS bar charts, and PDF export: Pending Proposals (active items and the step each is waiting on), Aging (open proposals ranked by total time in the workflow, day-bucketed), Bottleneck (per-step average and total active time across all history, work-in-progress count, and revisit count), Participation (per-reviewer assigned/acted/pending counts, average decision latency, and committee-vote turnout), User Activity (a single reviewer's decisions, votes, comments, and edits with time spent), and Historical Change (a single proposal's ordered decision timeline and time in each step). The append-only decision log now records a `submitted` transition so the stream is complete from step-zero entry, and a precomputed `{prefix}kenii_stage_durations` rollup , rebuilt idempotently on every transition and backfillable with `wp kenii rebuild-stage-durations [–commit]` , is the single source the reports read. Hold time is excluded from active time and tracked separately; a step re-entered after a return gets a new rollup row with an incremented visit number, and the bottleneck report sums every visit while surfacing the revisit count. Charts render identically on screen and in the PDF, which reuses the evidence-export Dompdf pipeline; every export is audit-logged. A gated Reports screen in the curriculum context carries the report picker, tables, charts, and per-report PDF export.
  • Added In-plugin documentation , , registrar-facing guides for the whole module (overview, proposals, workflows, committees and meetings, the review screen, validation rules and state packs, the substantive-change radar, and the evidence export) served through the in-plugin docs viewer.
  • Added Platform notification layer (`kenii_notify`): registered event catalog, role/user recipient resolution, per-user email opt-outs with a Notifications preferences screen in Settings. First events: addendum published, catalog year archived, license grace period.
  • Changed Brand alignment against the Kenii design system: form inputs adopt the brand treatment (10px radius, eyebrow labels, purple focus ring) through the shared field primitives; the sidebar's active item is now the brand's lime pill with dark text and an in-pill count badge; chart color 2 is the brand lime, and category bars cycle the five-token chart palette across Catalog, Pathways, and Curriculum report charts.
  • Changed Colorblind-safe status palette: Data Health and Pipeline good/ok/bad bars moved off green/amber/red onto a blue/amber/red set separated by lightness (`–kenii-status-*`), verified under deuteranopia and protanopia simulation, with non-color signals (labels, counts, warning icons) retained.
  • Changed Public frontend defaults align to the brand: Plus Jakarta Sans as the default font, the brand neutral scale, text and success colors , defaults only; sites with saved brand settings or custom CSS keep their exact look through the three-layer override system.
  • Changed Compare Catalog Years results render as separated entity cards with human names and design-system status badges; raw slug/key identifiers no longer appear, and a changed record expands to a field-level from → to view.
  • Changed Recent Activity and Recent Decisions lists across Catalog, Pathways, and Curriculum share one entity-type pill component with human labels, display names, and site-timezone dates.
  • Changed The Curriculum galaxy tile opens the module dashboard instead of the Proposals list, and the curriculum sidebar's settings entry reads "Curriculum Settings".
  • Changed Cross-module design-consistency pass (presentation only , no data model, behavior, report math, or REST contract changes). Catalog, Pathways, and Curriculum now share one visual system:
  • Changed Row actions , , a shared `RowActions` "primary + overflow" primitive (inline Edit plus a keyboard-accessible `⋯` menu, ≥24px targets) replaces the three previous patterns: Catalog's overflow-only menus, Pathways' bare Edit/Delete buttons, and the hand-rolled Catalog Years menu. It is now used by every list across all three modules (programs, program areas, credentials, courses, campuses, terms, policies, articulations, catalog years, pathways communities/institutions/programs, and curriculum proposals/workflows/meetings); the bespoke ContextMenu component was removed.
  • Changed Form fields , , the Add Course form no longer renders Course Code and Course Title at different heights; per-field size overrides were removed so both flow through the shared field primitive at one uniform height, padding, and typography. Course Code shows a `ACC-120` placeholder with a "Format: PREFIX-NUMBER" helper. Placeholder text across the admin was made example-form (never a real-looking value or institution name sitting in the box), and the small Pathways editors were moved onto the shared Field primitive.
  • Changed Charts , , a token-driven chart palette (`–kenii-chart-*`, anchored on the Kenii purple and green) replaces hardcoded hex values; the Catalog overview bars and the Curriculum report bars (Aging/Bottleneck/timeline) now draw from the same tokens.
  • Changed gains a module Overview dashboard matching the Catalog and Pathways dashboards (pipeline, needs-attention, recent decisions), and a Curriculum Settings hub that houses Validation and State Pack; the State Pack screen is reorganized into an Active-pack card, an Import card, and a drag-and-drop upload dropzone. Curriculum remains license-gated.
  • Changed Curriculum UI polish pass (presentation only , no data model, behavior, report math, or REST contract changes): decluttered every curriculum screen to one short helper line per section with the fuller narration behind disclosures or in Help; removed all command-line/developer affordances from the customer UI; State Pack reference-library search now filters as you type (single debounced field, no Search button) with a compact archived-courses toggle; the Validation required-fields grid gained a per-proposal-type "Require all / Clear" control; and the report charts (Aging buckets, Bottleneck per-step bars, Historical Change timeline) are now HTML/CSS bars that render identically on screen and in the Dompdf PDF, replacing the inline SVG that would not lay out in Dompdf.
  • Fixed An approved and materialized proposal's redline no longer renders empty: the target's field values are frozen at submission and the redline diffs against that snapshot, so the record of what changed survives the change itself being applied.
  • Fixed The Compare button on Compare Catalog Years aligns with the year selects, and the diff result cards are visually separated.
  • Fixed HTML entities imported into titles now decode at the intake boundary; `wp kenii decode-entities [–commit]` repairs existing records. The repair now also decodes stored taxonomy term names across the Kenii taxonomies (program areas, program/credential types, course prefixes and types, delivery modes, transfer institutions, catalog years), and term create/rename persist the decoded name so an ampersand renders as `&` everywhere , management screens, the program-editor area picker, and the front end , instead of a literal `&`.
  • Fixed The Program Areas management screen now uses the standard Catalog admin page shell (page padding, page-header block, and card/table spacing) so it matches the Programs, Credentials, and Courses lists instead of hugging the top of the panel.
  • Fixed Curriculum report PDFs rendered their charts as a run-together text blob above the table because Dompdf does not lay out inline SVG; charts are now HTML/CSS bars that render cleanly in the PDF.