Skip to content

10.0.0-preview - #631

Merged
goranalkovic-infinum merged 169 commits into
mainfrom
feature/ui-updates
Aug 6, 2026
Merged

10.0.0-preview#631
goranalkovic-infinum merged 169 commits into
mainfrom
feature/ui-updates

Conversation

@iruzevic

@iruzevic iruzevic commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Description

Broad UI, editor, and tooling overhaul for Eightshift Forms, plus integration and labels refinements. Migrates the styling system to Tailwind, reworks the block editor experience, and cleans up block markup and build config.

Note

This is 99% production ready, a few checks with older projects are still required for full release.

Added

  • Added a redesigned block editor experience with reusable option panels, status indicators, contextual help, and improved controls across forms, fields, integrations, conditional visibility, steps, and result outputs.
  • Added Tailwind CSS selector support and per-form and per-field style overrides for admin, editor, and frontend rendering.
  • Added new admin listing and pagination components to improve settings and listing navigation.
  • Added media attachment metadata support for checkbox and radio image alt text.

Changed

  • Updated custom blocks for WordPress 6.9+ compatibility through Block API v3.
  • Reworked admin settings and listings with updated controls, empty states, search guidance, status indicators, and actions.
  • Replaced component SCSS styling with Tailwind CSS classes using the esf: prefix across admin, editor, and frontend assets.
  • Renamed the tab background attribute from tabNoBg to tabWithBg, with tabs now displaying a background by default.
  • Updated multistep step indicators to retain class names when initialized dynamically and improve accessibility.
  • Updated dynamic HTML attribute output to use WordPress-safe escaping.
  • Updated PHP and JavaScript dependencies and added Tailwind CSS tooling.

Fixed

  • Prevented forms from being saved when required field names are empty and added clearer editor feedback for invalid field configuration.
  • Improved conditional visibility editing so field, operator, and value changes stay synchronized and unavailable states are handled clearly.
  • Improved accessibility feedback for missing labels, hidden labels, disabled and hidden fields, conditional rules, and checkbox and radio image alt text.
  • Fixed checkbox, radio, and select show as option handling in the editor.
  • Fixed editor option persistence for the label-as-placeholder setting and conditional visibility on external blocks.
  • Fixed editor previews and sizing for field widths and checkbox controls.

Removed

  • Removed the adminListingPagination component attribute; pagination state is now supplied through adminListingData.
  • Removed legacy component attributes ratingIsReadOnly, checkboxAsToggleSize, textareaIsMonospace, textareaSize, and textareaLimitHeight.
  • Removed legacy card-inline presentation props and replaced cardInlineTitleLink with cardInlineUrl.

QA Guide

Use a test site and an existing or newly created form.

  1. Open the Forms area in WordPress. Confirm settings pages and form listings load without visual errors.
  2. Create or edit a form:
    • Add several field types.
    • Move between the available settings tabs.
    • Change labels, required state, visibility, and disabled state.
    • Save the form, close it, and reopen it. Confirm the changes remain.
  3. Test field display options:
    • Checkboxes, radio buttons, and select fields should display correctly in each available style.
    • Field widths and previews should match the selected settings.
  4. Test conditional visibility:
    • Configure one field to show or hide based on another field.
    • Save and reopen the form.
    • Preview the form and confirm the field responds correctly when the controlling value changes.
  5. Test a multistep form:
    • Add multiple steps.
    • Move forward and backward between steps.
    • Confirm the step indicator updates correctly and remains accessible.
  6. Test the frontend form:
    • Check the layout on desktop and mobile.
    • Complete and submit the form.
    • Confirm validation messages, hidden fields, disabled fields, and the success state behave as expected.
  7. Check accessibility basics:
    • Use only the keyboard to move through the editor and frontend form.
    • Confirm visible labels and controls are understandable.
    • Confirm focus is visible and the tab order is logical.
  8. Return to the admin listings and test search, pagination, empty states, and available row actions.

For each issue, record the form used, the steps to reproduce it, the expected result, the actual result, and a screenshot.

Screenshots / Videos

Linked documentation PR

Comment thread AGENTS.md
@goranalkovic-infinum goranalkovic-infinum changed the title Feature/UI updates 10.0.0-preview Aug 6, 2026

@piqusy piqusy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review pass on this large UI/Tailwind migration PR. Left 6 line comments on the highest-confidence issues found (a global wp_kses caching regression, broken block icons from a key rename, a new escaping wrap that strips required aria/autocomplete attributes, a debug-email field regression, a dropped cache call, and a Tailwind prefix typo repeated in a few places). Everything else reviewed (REST routes, file-upload validation, Pardot token/cookie fixes, captcha labels, DI wiring, Labels/Fallback cleanup) checked out clean.

Comment thread src/View/EscapedView.php Outdated
Comment on lines +37 to +41
static $result = null;

/**
* Add forms additional attributes to allow list.
*
* @return array<string, array<string, bool>>
*/
private function setForm(): array
{
return self::FORM;
if ($result !== null) {
return $result;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setCustomWpksesPostTags() now memoizes its result in a static $result, ignoring the $tags/$context args on every call after the first. This is a global WordPress core filter (wp_kses_allowed_html), not something scoped to this plugin's own calls, and WP core itself invokes it with different contexts within the same request.

Concrete failure case (traced against WP core's kses.php): saving any post as a user without unfiltered_html (Editors, Authors, Contributors, even Admins on multisite) triggers, in the same wp_insert_post() call:

  1. title_save_prewp_filter_kses($title)wp_kses($title, 'title_save_pre') → hits the default branch in wp_kses_allowed_html() with $tags = $allowedtags (small comment-tag set). This filter fires first and gets cached.
  2. content_save_prewp_filter_post_kses($content)wp_kses($content, 'post') → should get $allowedposttags (full post tag set: p, div, h1-h6, img, table, etc.) but instead gets the cached title-context result from step 1.

Net effect: post_content gets silently stripped down to a near-comment-level allow-list for any non-superuser saving any post on the site. Suggest keying the cache by $context (or dropping the memoization — the original per-call computation was cheap array merges, not worth caching).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few reasonable arguments for memoizing the result:

  • Avoid repeated array work: array_merge() plus the loop over form elements runs on every wp_kses_allowed_html invocation.
  • The plugin additions are stable: self::FORM and the SVG allow-list do not change during a request.
  • It returns the same result for plugin-owned assumptions: If this callback were called only with one known $tags set and context, caching would be valid.
  • The result is relatively large: Avoiding repeated construction could reduce small amounts of allocation in requests with many KSES calls.

Those arguments support caching the context-independent plugin data, such as getSvg(), but not caching the complete callback result. The complete result depends on $tags, and WordPress invokes this global filter with different base allow-lists and contexts during one request. $context alone is also not a fully reliable cache key because callers could provide different $tags for the same context.

So the current implementation is defensible as a micro-optimization, but the cache is placed at the wrong boundary. The performance gain is likely minor compared with the correctness risk.

What I'll do is remove the outer static $result; retaining the getSvg() memoization.

Comment thread src/Blocks/custom/checkbox/manifest.json
@@ -130,7 +125,7 @@ class="<?php echo esc_attr($formsClass); ?>"

// Render blocks.
foreach ($output as $block) {
echo apply_filters('the_content', render_block($block)); // phpcs:ignore Eightshift.Security.HelpersEscape.OutputNotEscaped
echo wp_kses_post(apply_filters('the_content', render_block($block)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wp_kses_post() wrap is new in this PR (previously the output was echoed directly with a phpcs:ignore Eightshift.Security.HelpersEscape.OutputNotEscaped) — good escaping fix in principle, but the plugin's custom wp_kses allow-list (src/View/EscapedView.php, via vendor AbstractEscapedView::FORM) was never updated to include aria-required, aria-invalid, or autocomplete on <input>.

src/Blocks/components/input/input.php sets all three on every rendered input ($inputAttrs['autocomplete'], $inputAttrs['aria-required'], $inputAttrs['aria-invalid']). Since wp_kses_post() strips any attribute not in the allow-list, every rendered <input> on the frontend silently loses these attributes.

Suggest adding aria-required, aria-invalid, and autocomplete to the input entry in the FORM allow-list alongside this change.

Comment thread src/Integrations/Mailer/Mailer.php Outdated
// translators: %s replaces the debug key.
$body .= '<p style="font-family: monospace;">' . \sprintf(\wp_kses_post(\__('Debug Key: <strong>%s</strong>', 'eightshift-forms')), \esc_html($debugKeyValue)) . '</p>';

// translators: %s replaces the debug key description.
$body .= '<p style="font-family: monospace;">' . \sprintf(\wp_kses_post(\__('Debug Key description: <strong>%s</strong>', 'eightshift-forms')), \esc_html($this->settingsFallback->getFlagLabel($debugKeyValue))) . '</p>';
$body .= '<p style="font-family: monospace;">' . \sprintf(\wp_kses_post(\__('Debug Key description: <strong>%s</strong>', 'eightshift-forms')), \esc_html(Labels::getLabel($debugKeyValue))) . '</p>';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Debug Key description" field switched from SettingsFallback::getFlagLabel() (returned the flag's technical 'label' field, e.g. "Captcha feature is disabled.") to Labels::getLabel(), which only ever returns the flag's 'output' field (src/Labels/Labels.php:429self::getFlagsList()[$key]['output']) — the same public-facing message already shown elsewhere in this email.

Result: admins receiving a Mailer fallback/troubleshooting email now see the same generic public-facing message duplicated in both the main message and the "Debug Key description" line, losing the more technical detail the old label field provided for triage.

If the intent was just to drop the SettingsFallbackDataInterface dependency, Labels may need a variant that exposes the description/label field rather than reusing getLabel()'s public-output value here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check this between the v10 preview and the full release, thanks

Comment thread src/Helpers/GeneralHelpers.php Outdated
@@ -664,8 +660,8 @@ public static function getBlockLocations(string $formId, string $type): array

$isDeveloperModeActive = DeveloperHelpers::isDeveloperModeActive();

$output = \array_map(
function ($item) use ($isDeveloperModeActive) {
return \array_map(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refactor inlined the final array_map(...) directly into the return, dropping the \wp_cache_add($cacheKey, $output, $cacheGroup, \HOUR_IN_SECONDS); call that used to run right after building $output (and before the old return $output;).

Only the "no matching posts" branch a few lines up (if (!$items) { \wp_cache_add(...); return []; }) still populates the cache now. Any call that actually finds block locations — the common case, used by admin form/result listing pages — always misses cache and re-hits $wpdb->get_results() on every call instead of once per hour.

Suggest restoring the wp_cache_add() call for this branch too, e.g. by assigning to a variable before returning.

Comment thread src/Blocks/components/admin-listing/admin-listing.php
@goranalkovic-infinum
goranalkovic-infinum merged commit 02abfd0 into main Aug 6, 2026
2 checks passed
@goranalkovic-infinum
goranalkovic-infinum deleted the feature/ui-updates branch August 6, 2026 14:25
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.

[FEATURE] - Convert all CSS to Tailwind

4 participants