Skip to content

Conversation

@SebastianWiz
Copy link
Contributor

Context

⛑️ Ticket(s): No active tickets currently, but I've helped multiple customers with similar requests in the past, so there's definitely demand for this.

Summary

Adds a customizable shimmer animation and optional rotating SVG spinner to the GCOAI's Stream field loading placeholder. This replaces the static "Loading..." text with a more polished, animated loading indicator.

This is especially helpful now that we support GPT-5 models with reasoning capabilities, which take significantly longer to respond. An animated loading indicator provides better user feedback during these longer wait times compared to static text.

Before:
static-loading

After:
anmited-loading

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a new PHP class GCOAI_Loading_Animation that provides configurable shimmer loading markup and an optional spinner for Gravity Forms OpenAI stream placeholders, registers filters/actions to replace placeholders, and injects/init styles and scripts to reapply animations after AJAX/regenerate events.

Changes

Cohort / File(s) Summary
New Loading Animation Class
gc-openai/gcoai-stream-loading-text-animation.php
Adds GCOAI_Loading_Animation with constructor args (text, base_color, shimmer_color, shimmer_duration, show_shimmer, show_spinner, spinner_size, form_id). Implements filter_loading_text(), register_init_script(), get_shimmer_markup(), output_styles() and internal get_styles_css(). Hooks into Gravity Forms and WP to substitute placeholders, inject CSS/JS once, and reapply on Generate/Regenerate and AJAXComplete.

Sequence Diagram(s)

sequenceDiagram
    participant WP as WordPress
    participant GF as Gravity Forms
    participant GCOAI as GCOAI_Loading_Animation
    participant Browser as Browser JS

    WP->>GCOAI: instantiate(config)
    GCOAI->>WP: add_filter(gform_gcoai_field_loading_text)
    GCOAI->>WP: add_action(gform_register_init_scripts)

    GF->>GCOAI: filter_loading_text(placeholder, field, form)
    alt target matches
        GCOAI->>GF: return shimmer + optional spinner markup
    else
        GCOAI->>GF: return original placeholder
    end

    WP->>GCOAI: gform_register_init_scripts(form, is_ajax)
    GCOAI->>Browser: inject CSS & init script (idempotent)
    Browser->>Browser: apply/animate shimmer and spinner
    Browser->>Browser: on AJAXComplete / regenerate -> reapply markup
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review hooking points and filter/action signatures in gc-openai/gcoai-stream-loading-text-animation.php.
  • Verify form/field matching and guard conditions to avoid unintended placeholder replacement.
  • Inspect CSS/JS output for idempotence, scoping, and accessibility (spinner SVG and animation performance).

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description includes context explaining the motivation and a clear summary of the changes. However, it lacks proper formatting with the required section structure from the template. Format the description according to the template structure with clearly marked sections (Context, Summary) using the exact headings and separators specified in the template.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing a new PHP snippet for customizing the Stream field's loading text with animation.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4362519 and 4e3a704.

📒 Files selected for processing (1)
  • gc-openai/gcoai-stream-loading-text-animation.php (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Nov 15, 2025

Warnings
⚠️ When ready, don't forget to request reviews on this pull request from your fellow wizards.

Generated by 🚫 dangerJS against 4362519

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
gc-openai/gcoai-stream-loading-text-animation.php (1)

30-91: JS init script is solid; consider handling $is_ajax and gform timing

The script injection, use of wp_json_encode, and re-applying the shimmer after clicks/AJAX all look correct for the intended UX.

Two minor points:

  1. $is_ajax is never used, which PHPMD has flagged. If you want a clean run, either:

    • Prefix the parameter (e.g., $_is_ajax) or
    • Add a small no-op reference ($unused = $is_ajax;) or PHPMD suppression, while keeping the signature compatible with the hook.
  2. window.gform.addFilter is checked at script-evaluation time. If gform is sometimes defined later, you might prefer to move that block into the $(function() { ... }) callback so the filter registration runs after the Gravity Forms scripts are loaded.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c61ee6a and 45b8256.

📒 Files selected for processing (1)
  • gc-openai/gcoai-stream-loading-text-animation.php (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: PHP Lint (PR)
gc-openai/gcoai-stream-loading-text-animation.php

[error] 5-5: Whitespace found at end of line (Squiz.WhiteSpace.SuperfluousWhitespace.EndLine).

🪛 GitHub Check: PHPCS (Files Changed)
gc-openai/gcoai-stream-loading-text-animation.php

[failure] 147-147:
Whitespace found at end of line


[failure] 139-139:
Whitespace found at end of line


[failure] 131-131:
Whitespace found at end of line


[failure] 123-123:
Whitespace found at end of line


[failure] 110-110:
Whitespace found at end of line


[failure] 95-95:
Whitespace found at end of line


[failure] 45-45:
Whitespace found at end of line


[failure] 34-34:
Whitespace found at end of line


[failure] 6-6:
Whitespace found at end of line


[failure] 5-5:
Whitespace found at end of line

🪛 PHPMD (2.15.0)
gc-openai/gcoai-stream-loading-text-animation.php

30-30: Avoid unused parameters such as '$is_ajax'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (4)
gc-openai/gcoai-stream-loading-text-animation.php (4)

13-28: Constructor defaults and hook registration look good

Defaults are sensible, and hooking into gform_gcoai_field_loading_text and gform_register_init_scripts with the correct argument counts is appropriate for this behavior.


93-117: Shimmer markup generation and escaping look correct

get_shimmer_markup() builds predictable markup, and the use of esc_attr for SVG attributes and esc_html for the text protects against accidental injection from configuration values. Behavior matches the documented options for shimmer and spinner.


143-158: CSS generation is reasonable and properly parameterized

get_styles_css() correctly threads through the configurable base color, shimmer color, and duration into the CSS string, and the resulting rules cover both the spinner and shimmer text. Given that the inputs come from trusted configuration and are escaped, this looks good for the intended snippet use case.


163-173: Default configuration block is clear and matches documented behavior

The example instantiation with comments for form_id and field_id makes it easy to copy/paste and scope the animation, and the defaults (text + shimmer + spinner) align with the PR description.

Comment on lines 119 to 141
public function filter_loading_text( $placeholder, $field, $form = null ) {
if ( ! class_exists( '\\GC_OpenAI\\Fields\\Stream' ) || ! $field instanceof \GC_OpenAI\Fields\Stream ) {
return $placeholder;
}

// If form_id is specified, only apply to those forms
if ( $this->args['form_id'] !== null ) {
$form_ids = is_array( $this->args['form_id'] ) ? $this->args['form_id'] : array( $this->args['form_id'] );
if ( $form && ! in_array( rgar( $form, 'id' ), $form_ids ) ) {
return $placeholder;
}
}

// If field_id is specified, only apply to those fields
if ( $this->args['field_id'] !== null ) {
$field_ids = is_array( $this->args['field_id'] ) ? $this->args['field_id'] : array( $this->args['field_id'] );
if ( ! in_array( rgar( $field, 'id' ), $field_ids ) ) {
return $placeholder;
}
}

return $this->get_shimmer_markup();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

field_id targeting is broken due to use of rgar on a field object

In filter_loading_text(), the form scoping uses rgar( $form, 'id' ), which is fine because $form is an array. For fields, however, you already check $field instanceof \GC_OpenAI\Fields\Stream, so $field is an object. Passing it to rgar( $field, 'id' ) will always produce an empty string, meaning the field_id filter never matches and the shimmer is never applied when field_id is configured.

You can fix this by reading $field->id directly and normalizing types for comparison:

-		// If field_id is specified, only apply to those fields
-		if ( $this->args['field_id'] !== null ) {
-			$field_ids = is_array( $this->args['field_id'] ) ? $this->args['field_id'] : array( $this->args['field_id'] );
-			if ( ! in_array( rgar( $field, 'id' ), $field_ids ) ) {
-				return $placeholder;
-			}
-		}
+		// If field_id is specified, only apply to those fields
+		if ( $this->args['field_id'] !== null && isset( $field->id ) ) {
+			$field_ids = is_array( $this->args['field_id'] ) ? $this->args['field_id'] : array( $this->args['field_id'] );
+			// Normalize IDs to strings for consistent comparison.
+			$field_ids = array_map( 'strval', $field_ids );
+
+			if ( ! in_array( (string) $field->id, $field_ids, true ) ) {
+				return $placeholder;
+			}
+		}

Also, if there are code paths where this filter is called without a $form value, form_id scoping will be skipped entirely; it’s worth confirming the filter’s usage in GC OpenAI to ensure that’s acceptable.


I'll verify the review comment by examining the codebase to understand the issue with rgar() function and the field object handling.

I've confirmed from Gravity Forms documentation that the rgar() function is used to retrieve a value from an array, while rgobj() is used to retrieve a property value from an object. This validates the core of the review comment. Now I need to verify the codebase specifics.


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for filter_loading_text calls and hook registration
rg -n "filter_loading_text" --type=php

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Find the Stream class to check if it has an 'id' property
fd -e php | xargs grep -l "class Stream" | head -5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for add_filter/add_action calls that might register this filter
rg -n "add_filter.*placeholder" --type=php -B2 -A2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for gcoai-stream-loading-text-animation.php to understand filter registration
fd "gcoai-stream-loading-text-animation.php"

Length of output: 49


Let me examine the file content and find the Stream class definition:


---

🏁 Script executed:

```shell
#!/bin/bash
# Read the full file to understand context and the filter registration
cat gc-openai/gcoai-stream-loading-text-animation.php

Length of output: 6227


🏁 Script executed:

#!/bin/bash
# Search for Stream class more broadly
rg -n "class.*Stream" --type=php -A5

Length of output: 560


🏁 Script executed:

#!/bin/bash
# Search for where the filter gform_gcoai_field_loading_text is called/applied
rg -n "gform_gcoai_field_loading_text" --type=php -B3 -A3

Length of output: 603


🏁 Script executed:

#!/bin/bash
# Check if rgar function is defined in codebase or if it's from Gravity Forms
rg -n "function rgar\|function rgobj" --type=php

Length of output: 0


Now I need to verify the behavior of rgar() when passed an object, and confirm the Stream class structure. Let me search for more context:





🏁 Script executed:

#!/bin/bash
# Search for any composer.json or dependency declarations to understand GC_OpenAI origin
fd "composer.json\|package.json\|requirements.php" -type f

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Look for any GC_OpenAI class definitions or where Stream is defined
rg -n "GC_OpenAI.*Fields.*Stream" --type=php -B2 -A10

Length of output: 1226


🏁 Script executed:

#!/bin/bash
# Check if there are any other files that reference Stream or use field->id directly
rg -n "\$field->id" --type=php -C2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for filter callers to understand who calls gform_gcoai_field_loading_text
rg -n "apply_filters.*gform_gcoai_field_loading_text\|do_filter.*gform_gcoai_field_loading_text" --type=php -B3 -A3

Length of output: 0


🌐 Web query:

Gravity Forms rgar function behavior with objects instead of arrays

💡 Result:

Short answer: rgar() expects arrays — it checks array keys and will not read object properties. For Gravity Forms objects (fields, etc.) use rgobj() or object property access (->), or cast the object to an array first.

Why and examples:

  • rgar() is documented as “Get a specific property of an array” and is intended for associative arrays (returns null if key missing). [1]
  • For objects Gravity Forms provides rgobj() to get an object property. Many GF structures (fields since v1.9) use object notation ($field->inputs). [2][3]

Examples:

  • Array (use rgar):
    $value = rgar($array, 'key'); // works. [1]

  • Object (use rgobj or ->):
    $value = rgobj($field, 'inputs'); // recommended. [2]
    // or
    $value = isset($field->inputs) ? $field->inputs : null; // direct object access. [3]

Alternative: cast object to array if you must use rgar:
$value = rgar((array)$obj, 'property');

References:
[1] rgar() docs.
[2] rgobj() docs.
[3] Gravity Forms v1.9 developer notes (object notation / field objects).


Fix the field_id targeting by accessing the field object property directly, not via rgar()

The review comment is accurate. The rgar() function is designed for arrays and will not properly read object properties. Since $field is confirmed to be an instance of \GC_OpenAI\Fields\Stream (an object), using rgar($field, 'id') will fail to access the id property. This means field_id targeting never matches.

The suggested fix is correct:

  • Replace rgar($field, 'id') with $field->id
  • Add type normalization and strict comparison as shown
  • The form parameter concern is also valid (can be null, so form_id filtering is skipped when form is not provided)

Apply the exact diff from the review comment to fix the bug at lines 131–140.

🧰 Tools
🪛 GitHub Check: PHPCS (Files Changed)

[failure] 139-139:
Whitespace found at end of line


[failure] 131-131:
Whitespace found at end of line


[failure] 123-123:
Whitespace found at end of line

🤖 Prompt for AI Agents
In gc-openai/gcoai-stream-loading-text-animation.php around lines 119 to 141,
the field_id targeting uses rgar($field, 'id') which fails because $field is an
object; replace the rgar() call with direct object property access ($field->id),
normalize field_id(s) to an array and compare using strict comparison (===) to
ensure types match, and keep the existing guard that skips field_id filtering if
$this->args['field_id'] is null so behavior when $form is null remains
unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
gc-openai/gcoai-stream-loading-text-animation.php (4)

32-95: Fix trailing whitespace to pass PHPCS/pipeline checks.

Lines 34, 45, and 95 contain trailing whitespace causing the pipeline to fail. Remove all spaces/tabs at the end of these lines.

Based on static analysis hints and pipeline failures.


Note: Unused parameters are required by the WordPress hook signature.

PHPMD flags $field_values and $is_ajax as unused, but they're part of the gform_register_init_scripts hook signature and cannot be removed. You can suppress this warning with a PHPDoc comment if desired:

/**
 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 */
public function register_init_script( $form, $field_values, $is_ajax ) {

Based on static analysis hints.


97-107: Fix trailing whitespace to pass PHPCS/pipeline checks.

Line 105 contains trailing whitespace causing the pipeline to fail. Remove all spaces/tabs at the end of this line.

Based on static analysis hints and pipeline failures.


109-133: Fix trailing whitespace to pass PHPCS/pipeline checks.

Lines 110, 123, and 131 contain trailing whitespace causing PHPCS failures. Remove all spaces/tabs at the end of these lines.

Based on static analysis hints.


135-149: Fix trailing whitespace to pass PHPCS/pipeline checks.

Lines 139 and 147 contain trailing whitespace causing PHPCS failures. Remove all spaces/tabs at the end of these lines.

Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45b8256 and 2f63853.

📒 Files selected for processing (1)
  • gc-openai/gcoai-stream-loading-text-animation.php (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: PHP Lint (PR)
gc-openai/gcoai-stream-loading-text-animation.php

[error] 105-105: Whitespace found at end of line. Squiz.WhiteSpace.SuperfluousWhitespace.EndLine.

🪛 GitHub Check: PHPCS (Files Changed)
gc-openai/gcoai-stream-loading-text-animation.php

[failure] 165-165:
String "@Keyframes shimmer { 0% { background-position: -4rem top; } 70%, 100% { background-position: 12.5rem top; } }" does not require double quotes; use single quotes instead


[failure] 162-162:
String "@Keyframes spinner-stroke { 0% { stroke-dasharray: 0 150; stroke-dashoffset: 0; } 47.5% { stroke-dasharray: 42 150; stroke-dashoffset: -16; } 95%, 100% { stroke-dasharray: 42 150; stroke-dashoffset: -59; } } " does not require double quotes; use single quotes instead


[failure] 161-161:
String "@Keyframes spinner-rotation { 100% { transform: rotate(360deg); } } " does not require double quotes; use single quotes instead


[failure] 160-160:
String ".spinner-rotate circle { stroke-linecap: round; animation: spinner-stroke 1.5s ease-in-out infinite; } " does not require double quotes; use single quotes instead


[failure] 159-159:
String ".spinner-rotate { transform-origin: center; animation: spinner-rotation 2s linear infinite; } " does not require double quotes; use single quotes instead


[failure] 158-158:
String ".shimmer-spinner { flex-shrink: 0; } " does not require double quotes; use single quotes instead


[failure] 157-157:
String ".shimmer-wrapper { display: inline-flex; align-items: center; gap: 8px; } " does not require double quotes; use single quotes instead


[failure] 156-156:
Whitespace found at end of line


[failure] 105-105:
Whitespace found at end of line

🪛 PHPMD (2.15.0)
gc-openai/gcoai-stream-loading-text-animation.php

32-32: Avoid unused parameters such as '$field_values'. (undefined)

(UnusedFormalParameter)


32-32: Avoid unused parameters such as '$is_ajax'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (3)
gc-openai/gcoai-stream-loading-text-animation.php (3)

1-13: LGTM!

The class structure is clean with appropriate property visibility and a static flag to prevent duplicate style output.


14-30: LGTM!

The constructor properly initializes configurable arguments with sensible defaults and registers appropriate WordPress hooks for loading text filtering, script initialization, and style output.


169-180: LGTM!

The default configuration provides sensible production-ready values with clear inline documentation for form targeting. The "Thinking..." text and subtle animation parameters create a polished loading experience.

@saifsultanc saifsultanc merged commit 2b7a9cf into master Nov 15, 2025
1 of 3 checks passed
@saifsultanc saifsultanc deleted the SebastianWiz-patch-1 branch November 15, 2025 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants