Skip to content

6.39.0 — quiz scoring and grading, choice-field limits, Certificates tab, plugin icon and "View details" - #1753

Merged
rpgmem merged 21 commits into
mainfrom
develop
Oct 13, 2026
Merged

rpgmem merged 21 commits into
mainfrom
develop

Conversation

@rpgmem

@rpgmem rpgmem commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Release 6.39.0

Quizzes

  • Each field is scored on its own, with {{correct_count}} and {{question_count}} available in messages.
  • A quiz can be graded by hand on its own screen.
  • A quiz can reset when the participant leaves the window.

Choice fields

  • Options are written one per row.
  • A field can set minimum and maximum selections.

Certificates tab

  • New tab that holds the HTML tags and the required certificate fields (name, CPF/RF, e-mail).
  • Those fields are locked in the builder and validated on the client and the server.

Admin controls

  • Settings now use one control family: toggle, steps, and segmented.
  • The user dashboard toggles are reachable by keyboard again.

Plugin icon and "View details"

  • The plugin has its own icon.
  • "View details" shows the readme tabs, a Portuguese translation, and screenshots that open in place.

Fixes

  • Dark mode.
  • Form submits.
  • Failed quiz attempts.

The maintainer asked for this release in the conversation, so this PR is opened ready rather than as a draft.

Checklist

  • Version bumped in the three sites (ffcertificate.php header, FFC_VERSION, readme.txt Stable tag) (release: 6.39.0 — bump, finalize CHANGELOG, review docs #1752)
  • [Unreleased] renamed to [6.39.0] (2026-10-13), fresh empty [Unreleased] above, section condensed by story from 25 to 15 bullets (release: 6.39.0 — bump, finalize CHANGELOG, review docs #1752)
  • readme.txt == Upgrade Notice == rewritten for this version, ≤300 characters
  • @since tags written mid-batch corrected to 6.39.0: all 29 already said 6.39.0
  • Documentation cross-checked against every Added / Changed bullet
  • Minified assets current, checked by the "Verify minified assets are up to date" gate

After merge (maintainer / agent)

  1. Maintainer pushes the tag with the guarded recipe in CLAUDE.md.
  2. Agent resets develop to main (--force-with-lease).
  3. Agent opens the SHA-backfill PR for the CHANGELOG heading.
  4. Agent lists any branch left behind; maintainer deletes it.

🤖 Generated with Claude Code

https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA


Generated by Claude Code

rpgmem and others added 21 commits October 12, 2026 23:26
* feat(updates): give the plugin an icon on Dashboard → Updates

GitHubUpdater now sends an icons map (1x, 2x, svg) in the update object
and the View details payload, pointing at assets/img/brand/ in the
installed copy, so WordPress stops drawing its grey placeholder. The
directory also ships black, white, dark-screen and outline variants.
PluginIconAssetsTest ties each advertised URL to a file of the right
size that the release zip keeps.

Closes #1712

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(updates): ship every icon variant as PNG too

Each SVG variant now has a 128 px and a 256 px PNG beside it in
assets/img/brand/, rendered from the SVG, and PluginIconAssetsTest
checks every one exists at its size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* docs(changelog): name the PNG variants

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…lay (#1717)

* feat(updates): fill the View details modal with a banner and the readme's tabs

Closes #1714

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(updates): translate View details and open screenshots in an overlay

Closes #1716

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(updates): read the asset suffix instead of assuming .min

SCRIPT_DEBUG may already be defined by an earlier test in the process,
which made the enqueued URL drop the .min suffix in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…1720)

The rate-limit window tests, the certificates dashboard midnight test and
the export filename test read the expected value from the clock once,
before or after the call, against code that rounds the current time to a
minute, hour, day, week, month or site-timezone midnight. A run that
crossed the boundary between the two reads failed with nothing changed.
They now read it before and after and accept either.

CLAUDE.md records the clock rule, and that a readme.txt change carries
languages/readme-pt_BR.txt in the same PR, with the release cross-check.

Closes #1718
Closes #1719


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
The compiler wrote every .po header and no domain. It now writes WP-CLI's
shape: domain from X-Domain, six headers in WP-CLI's order, then
messages, with an integer-like key bare as PHP stores it. The recompiled
catalogue is byte-identical to `wp i18n make-php`'s output; only the
header keys changed. CLAUDE.md records the shape, and how to run the
fresh-install checks against the container's MariaDB without the
uninstall step that would delete a symlinked checkout.

Closes #1721


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
…1724)

* fix(admin): extend dark mode to native controls and fix the docs navigation overflow

- Declare color-scheme: dark on admin screens, so scrollbars and the
  time picker's clock follow the theme.
- Style the colour picker's label and Iris popup, .button-link,
  checkboxes and radios with the palette.
- Make the Quick Navigation search box fill its card; it was 25em in a
  280px column and scrolled the card sideways.
- Restore the padding core's .widefat tfoot rule removed from the
  Geolocation "Add new location" fields.

Closes #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): cover links, title actions, notices and core boxes in dark mode

A sweep of every FFC admin screen in Chromium flagged what core paints by
itself on the dark canvas: links (1.87-2.65:1), "Add New" / "Export CSV",
disabled primary buttons (core uses !important), colour inputs, the file
button, notice links, the Publish box footer, row actions and Screen
Options. Each now reads the palette.

Also: the form builder's row title reads the text colour instead of the
grip's grey, the submissions ID column is sized for six digits, and the
QR generator's required mark uses ffc-required.

Refs #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(audience): give the booking details title the theme's text colour

The admin modal is appended to <body>, outside .wrap, so its heading took
core's near-black h3 and vanished on the dark card.

Refs #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the dark disabled primary button a forced-colors contour

ForcedColorsContourTest caught it: the rule paints a ground, so under
forced colours the button would lose its boundary.

Refs #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ates screens (#1726)

* fix(admin): extend dark mode to native controls and fix the docs navigation overflow

- Declare color-scheme: dark on admin screens, so scrollbars and the
  time picker's clock follow the theme.
- Style the colour picker's label and Iris popup, .button-link,
  checkboxes and radios with the palette.
- Make the Quick Navigation search box fill its card; it was 25em in a
  280px column and scrolled the card sideways.
- Restore the padding core's .widefat tfoot rule removed from the
  Geolocation "Add new location" fields.

Closes #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): cover links, title actions, notices and core boxes in dark mode

A sweep of every FFC admin screen in Chromium flagged what core paints by
itself on the dark canvas: links (1.87-2.65:1), "Add New" / "Export CSV",
disabled primary buttons (core uses !important), colour inputs, the file
button, notice links, the Publish box footer, row actions and Screen
Options. Each now reads the palette.

Also: the form builder's row title reads the text colour instead of the
grip's grey, the submissions ID column is sized for six digits, and the
QR generator's required mark uses ffc-required.

Refs #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(audience): give the booking details title the theme's text colour

The admin modal is appended to <body>, outside .wrap, so its heading took
core's near-black h3 and vanished on the dark card.

Refs #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): dark mode on the Personal Calendars and Certificate templates screens

Their lists and editors are WordPress post screens that
AdminAssetsManager::is_ffc_page() never matches, so the toggle that puts
ffc-dark-mode on <html> never loaded and they rendered light.
SelfSchedulingAdmin and CertTemplateAdminScreen now enqueue the palette
and the toggle themselves. The false TOGGLE_NOT_NEEDED reason for the
calendar editor is rewritten, and the stale SelfSchedulingAdmin entry
leaves the register.

The rich-text e-mail editor's tabs, toolbar and status bar, and the
"Add title" placeholder, now read the palette.

Closes #1725

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the dark disabled primary button a forced-colors contour

ForcedColorsContourTest caught it: the rule paints a ground, so under
forced colours the button would lose its boundary.

Refs #1723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the dark active TinyMCE button a forced-colors contour

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
… offered (#1728)

Fields, selects, textareas and buttons inherit the font the labels use
instead of the browser's own. Public Design offers the same three fonts
as Email Signature Generator and Institutional Email: the theme's (the
default), the plugin's system font, or Arial.

Closes #1727


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
…rries limits (#1733)

A "Checkbox" field fell through to a generic input styled as a 45px text
box, ignored its options and posted `on`. Options were one
comma-separated string, so none could contain a comma. Required, length,
range and "is one of the options" were enforced by the browser only.

- Core\FormField reads a field's options (the new `choices` list or the
  legacy string) and its limits, and sanitises an editor row.
- The field builder edits options one per row, shuffles, caps checkbox
  selections, and takes min/max length (ceilings 255 / 2,000), number
  ranges with decimal places and date ranges. A new row is a copy of
  the server template instead of a second markup built in JS.
- Frontend\FormFieldRenderer renders radio/checkbox groups as a
  fieldset, the limits as attributes and a hint, and a counter;
  ffc-form-fields.js shuffles, counts and enforces the cap.
- Submission\FieldRulesGuard enforces every rule on the server.

Closes #1729


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
…s protect their fields (#1735)

* feat(forms): checkbox fields render their options, and every field carries limits

A "Checkbox" field fell through to a generic input styled as a 45px text
box, ignored its options and posted `on`. Options were one
comma-separated string, so none could contain a comma. Required, length,
range and "is one of the options" were enforced by the browser only.

- Core\FormField reads a field's options (the new `choices` list or the
  legacy string) and its limits, and sanitises an editor row.
- The field builder edits options one per row, shuffles, caps checkbox
  selections, and takes min/max length (ceilings 255 / 2,000), number
  ranges with decimal places and date ranges. A new row is a copy of
  the server template instead of a second markup built in JS.
- Frontend\FormFieldRenderer renders radio/checkbox groups as a
  fieldset, the limits as attributes and a hint, and a counter;
  ffc-form-fields.js shuffles, counts and enforces the cap.
- Submission\FieldRulesGuard enforces every rule on the server.

Closes #1729

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* wip: #1734

* feat(forms): standard fields validate as they claim, and required tags protect their fields

- cpf_rf: an invalid CPF/RF blocks the submit through custom validity and
  its message stays until corrected; maxlength 14, numeric keyboard.
- email: an invalid address is reported as such, checked with is_email(),
  instead of "Email address is required."
- Name fields need at least two words and 4 characters, in the browser
  and in FieldRulesGuard.
- "Required Certificate Tags" moves from Advanced to a new Certificates
  tab; the layout editor and the template screen list the configured
  tags instead of a fixed three.
- A field a required tag calls for is badged and amber in the builder,
  always required, with a read-only name and no Remove; saving a form
  without one warns which tag has no field.

Closes #1734

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(forms): forced-colors contour for the required-tag badge, canonical email in the sanitizer

The badge and the locked field name paint only a ground, which forced-colors
discards, so both take a contour. The sanitizer canonicalises the address
before judging it, as IdentifierIdiomTest requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ine (#1737)

The section read the ffc_settings array after get_post_array() had run every
element through sanitize_text_field(), which turns line breaks into spaces,
so the Save button stored the list on one line and it read as one tag. The
textarea is now read raw and sanitised as a textarea, and the reader also
splits on whitespace, so a list already stored on one line reads again.

Closes #1736


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
…dy exists (#1740)

ffc-form-fields.js prints before ffc-core.js and creates window.FFC, so the
core's `window.FFC = window.FFC || {…}` skipped its own object and every
certificate form submit failed on FFC.request is not a function. The core now
installs its API over an existing namespace, keeping what was already there,
and keeps modules registered before it loaded.

Closes #1739


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
…box and range answers (#1738)

* feat(quiz): per-field scoring with correct answers, exact-match checkbox and range answers (in progress)

QuizQuestion reads a field's quiz configuration with the pre-#1730 rule as the
fallback; QuizScorer scores choice fields by correct option(s) or by points per
option, text when answered and number/date by a correct range; the editor gets
a per-field quiz block with per-option correct marks and points; show-correct
marks each question right or wrong; {{correct_count}} and {{question_count}}.

Tests, documentation and catalogues follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(quiz): tests, documentation and catalogues for per-field scoring

QuizScorer and QuizQuestion tests (each mode, legacy forms, the exact checkbox
set, the floored sum, ranges, the ampersand an option can carry); the save
handler stores the option rows and fits the question; the builder renders the
aligned marks and the quiz block; the per-question verdict on the form. The
Quiz topic describes the modes and the two new tokens; the verdict and the
option mark take a context so "Correct" does not read as the verb. A level-9
row-shape cast is fixed in the persister.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(quiz): the admin quiz toggle test no longer expects the old points input

The per-field quiz settings moved to the field builder, which has its own
tests; this one keeps checking the form-level settings the toggle shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(quiz): stub _x where the form editor renders the field builder

The option mark now takes a translation context, and the metabox renderer
test renders the builder rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
Settings → Certificates keeps the layout tags and, apart, a table of the
fields every certificate form must have (name, label, type; default
name, cpf_rf, email). Email cannot be removed. {{auth_code}} stays
forced; {{schedule}} is asked only of a form with an Event Schedule.

A new form starts with the table's fields; in the builder their name,
type and required flag are locked, and a form missing one offers an
"Add the required fields" button. The save handler forces the table's
type. An install that never saved the table derives it from the old
tag list, and keeps the schedule check until the tab is saved again.

Refs #1741


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
…s, manual grading (#1743)

* feat(quiz): optional reset when the participant leaves the window

A quiz setting, off by default, clears the scored answers when the
participant switches tabs or minimises the browser. It listens to
visibilitychange, not blur, so a notification, a screen reader or a
click into an embedded video does not trigger it. The form warns before
the participant starts, and on return an alertdialog (the shared modal)
says why. Name, CPF/RF and unscored fields stay.

Leaving is not an attempt. The browser counts the exits in a hidden
field kept in sessionStorage; a reset quiz stores the count, capped at
999, as _quiz_focus_losses, shown read-only on the submission screen.
A deterrent, not a lock, and the documentation says so.

Refs #1731

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(quiz): an attempt that did not pass is not a certificate

process_submission() inserts every quiz attempt as an issued certificate
and schedules the email pipeline; the persister lowers the status to the
attempt's result right after. Nothing downstream read the status, so a
first attempt that failed was emailed a working certificate link and its
auth code verified.

Core\SubmissionStatus names the statuses and which of them is a
certificate. The async pipeline, verification by auth code and by magic
link, the REST lookup and the user dashboard (list and count) now skip a
quiz attempt that did not pass.

Closes #1744

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(quiz): manual grading of open answers

A text or textarea question can be set to "Manual grading". An attempt
with such an answer is stored as "Quiz: Awaiting Grading": no result,
no certificate, and the same CPF/RF cannot start another attempt while
it waits. The submission edit screen grades it through its own form
(admin_post_ffc_grade_submission, capability ffc_grade_certificates,
seeded from ffc_edit_certificates); saving publishes the attempt or
records Retry/Failed, and emails the participant the result through
two new editable templates.

Closes #1732

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(quiz): seed the grade-cap flag in the integration harness

The bootstrap smoke tests stub every one-shot capability flag so the
Loader does not run migrations against the harness's fake $wpdb; the
flag #1732 added was missing, so the grant ran and failed on
wpdb::get_blog_prefix().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
)

* feat(quiz): strict window-leave level on a reusable three-position switch

The quiz reset on leaving now has three levels stored in
quiz_reset_on_leave (0 Off, 1 Moderate, 2 Strict; existing forms keep
their behaviour). Strict also counts the window losing the focus, after
a global grace period (Settings -> Certificates), ignoring focus moving
into an iframe of the page. "Start again" reloads the page so shuffled
options are shuffled again.

Adds AdminUI::render_tri_toggle (severity and traffic palettes, pure
CSS over native radios) and an enum type to both autosave endpoints.

Closes #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(css): budget the three-position switch's pill radius

The track is 30px high and its ends are half that; the `pill` role
(12px) would leave them visibly squared, so the literal stays and the
budget says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): steps control replaces the three-position switch

AdminUI::render_steps() draws an ordered scale of 3 to 11 values as a
rail with one native radio per stop, filled up to the chosen one. It
takes palette (primary, severity, traffic), per-step tones, labels
(all or selected), size, shape and fluid; outside 3-11 options it
prints a select and calls _doing_it_wrong() naming the control that
fits. Styles live in the new ffc-control-* family in ffc-common.css,
through tokens only, with no radius or z-index literal (the radius
budget goes back to 5). The quiz's Leaving the Window level uses it
with the severity palette. CLAUDE.md gains a Controls section saying
which control fits which kind of value.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the steps fieldset's phpcs annotation a real reason

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
* feat(quiz): strict window-leave level on a reusable three-position switch

The quiz reset on leaving now has three levels stored in
quiz_reset_on_leave (0 Off, 1 Moderate, 2 Strict; existing forms keep
their behaviour). Strict also counts the window losing the focus, after
a global grace period (Settings -> Certificates), ignoring focus moving
into an iframe of the page. "Start again" reloads the page so shuffled
options are shuffled again.

Adds AdminUI::render_tri_toggle (severity and traffic palettes, pure
CSS over native radios) and an enum type to both autosave endpoints.

Closes #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(css): budget the three-position switch's pill radius

The track is 30px high and its ends are half that; the `pill` role
(12px) would leave them visibly squared, so the literal stays and the
budget says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): steps control replaces the three-position switch

AdminUI::render_steps() draws an ordered scale of 3 to 11 values as a
rail with one native radio per stop, filled up to the chosen one. It
takes palette (primary, severity, traffic), per-step tones, labels
(all or selected), size, shape and fluid; outside 3-11 options it
prints a select and calls _doing_it_wrong() naming the control that
fits. Styles live in the new ffc-control-* family in ffc-common.css,
through tokens only, with no radius or z-index literal (the radius
budget goes back to 5). The quiz's Leaving the Window level uses it
with the severity palette. CLAUDE.md gains a Controls section saying
which control fits which kind of value.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): ordered three-level settings use the steps control

The four geofence display modes, the two private-calendar displays and
the geolocation IP API fallback move from selects to
AdminUI::render_steps(), ordered from the most shown (or most
permissive) to the least. Stored values are unchanged, and a value the
scale does not know shows the select's old default. The API fallback
wears the severity palette; the display modes keep the neutral one.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the steps fieldset's phpcs annotation a real reason

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
* feat(quiz): strict window-leave level on a reusable three-position switch

The quiz reset on leaving now has three levels stored in
quiz_reset_on_leave (0 Off, 1 Moderate, 2 Strict; existing forms keep
their behaviour). Strict also counts the window losing the focus, after
a global grace period (Settings -> Certificates), ignoring focus moving
into an iframe of the page. "Start again" reloads the page so shuffled
options are shuffled again.

Adds AdminUI::render_tri_toggle (severity and traffic palettes, pure
CSS over native radios) and an enum type to both autosave endpoints.

Closes #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(css): budget the three-position switch's pill radius

The track is 30px high and its ends are half that; the `pill` role
(12px) would leave them visibly squared, so the literal stays and the
budget says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): steps control replaces the three-position switch

AdminUI::render_steps() draws an ordered scale of 3 to 11 values as a
rail with one native radio per stop, filled up to the chosen one. It
takes palette (primary, severity, traffic), per-step tones, labels
(all or selected), size, shape and fluid; outside 3-11 options it
prints a select and calls _doing_it_wrong() naming the control that
fits. Styles live in the new ffc-control-* family in ffc-common.css,
through tokens only, with no radius or z-index literal (the radius
budget goes back to 5). The quiz's Leaving the Window level uses it
with the severity palette. CLAUDE.md gains a Controls section saying
which control fits which kind of value.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): ordered three-level settings use the steps control

The four geofence display modes, the two private-calendar displays and
the geolocation IP API fallback move from selects to
AdminUI::render_steps(), ordered from the most shown (or most
permissive) to the least. Stored values are unchanged, and a value the
scale does not know shows the select's old default. The API fallback
wears the severity palette; the display modes keep the neutral one.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the steps fieldset's phpcs annotation a real reason

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): the on/off toggle joins the ffc-control family

`.ffc-toggle*` becomes `.ffc-control-toggle*` everywhere it is printed
(PHP, templates, JS-built markup, CSS and tests). The toggle gains the
family's hover and pressed halo, a greyed disabled state instead of an
opacity fade, and an optional `mark` that draws a check or a cross on
the knob from the Core\Icons registry (new Icons::MASKS custom
properties). The user dashboard's notification toggles drop their
second implementation, which hid the checkbox with display: none and
so took it out of keyboard reach.

Closes #1748

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): toggle mark layers on the token scale; dashboard radius budget locked in

The mark's z-index reads --ffc-z-raised and the knob keeps the default
stacking, so ffc-common.css adds no layer literal. Removing the
dashboard's private toggle switch left one radius literal fewer, so the
budget ratchets down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
… in a .form-table (#1751)

* feat(quiz): strict window-leave level on a reusable three-position switch

The quiz reset on leaving now has three levels stored in
quiz_reset_on_leave (0 Off, 1 Moderate, 2 Strict; existing forms keep
their behaviour). Strict also counts the window losing the focus, after
a global grace period (Settings -> Certificates), ignoring focus moving
into an iframe of the page. "Start again" reloads the page so shuffled
options are shuffled again.

Adds AdminUI::render_tri_toggle (severity and traffic palettes, pure
CSS over native radios) and an enum type to both autosave endpoints.

Closes #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* test(css): budget the three-position switch's pill radius

The track is 30px high and its ends are half that; the `pill` role
(12px) would leave them visibly squared, so the literal stays and the
budget says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): steps control replaces the three-position switch

AdminUI::render_steps() draws an ordered scale of 3 to 11 values as a
rail with one native radio per stop, filled up to the chosen one. It
takes palette (primary, severity, traffic), per-step tones, labels
(all or selected), size, shape and fluid; outside 3-11 options it
prints a select and calls _doing_it_wrong() naming the control that
fits. Styles live in the new ffc-control-* family in ffc-common.css,
through tokens only, with no radius or z-index literal (the radius
budget goes back to 5). The quiz's Leaving the Window level uses it
with the severity palette. CLAUDE.md gains a Controls section saying
which control fits which kind of value.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): ordered three-level settings use the steps control

The four geofence display modes, the two private-calendar displays and
the geolocation IP API fallback move from selects to
AdminUI::render_steps(), ordered from the most shown (or most
permissive) to the least. Stored values are unchanged, and a value the
scale does not know shows the select's old default. The API fallback
wears the severity palette; the display modes keep the neutral one.

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): give the steps fieldset's phpcs annotation a real reason

Refs #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): the on/off toggle joins the ffc-control family

`.ffc-toggle*` becomes `.ffc-control-toggle*` everywhere it is printed
(PHP, templates, JS-built markup, CSS and tests). The toggle gains the
family's hover and pressed halo, a greyed disabled state instead of an
opacity fade, and an optional `mark` that draws a check or a cross on
the knob from the Core\Icons registry (new Icons::MASKS custom
properties). The user dashboard's notification toggles drop their
second implementation, which hid the checkbox with display: none and
so took it out of keyboard reach.

Closes #1748

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): toggle mark layers on the token scale; dashboard radius budget locked in

The mark's z-index reads --ffc-z-raised and the knob keeps the default
stacking, so ffc-common.css adds no layer literal. Removing the
dashboard's private toggle switch left one radius literal fewer, so the
budget ratchets down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* feat(admin): segmented control for small unordered choices

AdminUI::render_segmented() draws 2 to 6 options with no order between
them as one row of native radios, the chosen one filled. Out of range it
prints a select and tells the developer. Dark Mode, Code Editor Theme and
the geofence Time Behavior use it, with the same stored values; the
autosave badge anchors after the control so it never breaks the
checked-label rule.

Closes #1750

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

* fix(admin): steps and segmented controls keep their layout in a .form-table

WordPress's forms.css makes every label in a .form-table fieldset
inline-block with a forced margin, which outranked the stop's flex column
and left each mark 28px left of its rail on every settings table that
adopted the steps control. Both controls' option labels now outrank it;
measured in a local WordPress, every mark sits on its stop's centre.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

---------

Co-authored-by: Claude <noreply@anthropic.com>
Bumps the three version sites, renames [Unreleased] to [6.39.0]
(2026-10-13) and condenses it by story (25 bullets to 15, every cited
issue kept), rewrites the Upgrade Notice, and brings the readme's Quiz
Mode line, in both languages, up to per-field scoring, manual grading
and the leave-the-window reset.


Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA

Co-authored-by: Claude <noreply@anthropic.com>
@rpgmem
rpgmem enabled auto-merge (squash) October 13, 2026 23:08
@rpgmem
rpgmem merged commit 00eb9c8 into main Oct 13, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant