fix(profile): add about character counter#431
Conversation
📝 WalkthroughWalkthroughThe profile about textarea now uses live binding and renders a Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app-modules/panel-app/src/Pages/ProfilePage.php`:
- Around line 135-139: Update the character-counting field configuration using
the visible hint callback and field chain to replace immediate live updates with
blur-triggered updates by setting live to onBlur mode. Preserve the existing
hint, length, row, and column settings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Pro
Run ID: e77e7255-6e0a-444e-a8b3-f599b58ccab0
📒 Files selected for processing (2)
app-modules/panel-app/src/Pages/ProfilePage.phpapp-modules/panel-app/tests/Feature/ProfilePageTest.php
| ->maxLength(500) | ||
| ->rows(4) | ||
| ->live(onBlur: true) | ||
| ->live() |
There was a problem hiding this comment.
Contador ficou bom. Uma observação (olhando pelo celular, não testei, então é mais hipótese): o número já é atualizado no cliente pelo Alpine, então o ->live() acaba não servindo pra contagem e dispara uma request Livewire a cada tecla à toa.
Acho que dá pra simplificar deixando o Livewire cuidar disso, sem Alpine.
| ->live() | |
| ->hint(fn (?string $state): string => mb_strlen($state ?? '') . '/500') | |
| ->live() |
Assim o ->live() passa a fazer sentido (é ele que re-renderiza o hint) e some o HtmlString, o aboutCharacterCounterHint(), o wire:ignore e o bloco de Alpine.
Trade-off: com ->live() cada tecla vira uma request; se pesar, dá pra usar ->live(onBlur: true). Como não testei, pode ser que tenha algum motivo pro Alpine que eu não vi. Se for, me corrige.
There was a problem hiding this comment.
Eu cheguei a testar uma abordagem nessa linha, deixando o hint(fn...) ser recalculado pelo Livewire com ->live(). Funciona, mas no teste manual o contador ficou dependente do round-trip do Livewire e apresentou atraso/inconsistência durante a digitação. Seguindo a sugestão do @gvieira18, vou ajustar para JsContent, que mantém o contador calculado no cliente, mas remove o HtmlString, o método auxiliar, wire:ignore e o bloco manual de Alpine. Assim o código fica mais simples e mais alinhado com o Filament.
gvieira18
left a comment
There was a problem hiding this comment.
Eu apresentei uma opção, o @Clintonrocha98 apresentou outra. Aguardamos mais sugestões
| Textarea::make('about') | ||
| ->label(__('panel-app::profile.fields.about')) | ||
| ->placeholder(__('panel-app::profile.placeholders.about')) | ||
| ->hint(fn (?string $state): HtmlString => $this->aboutCharacterCounterHint($state)) |
There was a problem hiding this comment.
Acho que um JsContent::make resolveria o problema e ficando somente do lado do AlpineJS, o que poderia remover* a dependência do ->live para o servidor contar e renderizar a cada alteração.
Algo assim:
| ->hint(fn (?string $state): HtmlString => $this->aboutCharacterCounterHint($state)) | |
| ->hint(JsContent::make('`${$state?.length ?? 0}/500`')) |
Note
Porém eu não sobre os casos extras tipo por algum motivo passou de 500 chars, qual vai ser o comportamento dele
There was a problem hiding this comment.
Se não me engano o usuário nao consegue passar dos 500
There was a problem hiding this comment.
Usar um array from resolve o problema de passar dos 500 caracteres e também de emojis que podem ter um tamanho maior
->hint(JsContent::make('${Array.from($state ?? "").length}/500'))
There was a problem hiding this comment.
Usar um array from resolve o problema de passar dos 500 caracteres e também de emojis que podem ter um tamanho maior
->hint(JsContent::make('
${Array.from($state ?? "").length}/500'))
Bem lembrado, emojis podem ter de 2 a 4 chars
|
Sugiro criar um teste com inserção de emojis para ver o comportamento do input. |
|
Revisei e testei localmente esse PR. Registrando os testes que fiz:
|
|
@ewbenigno Mas se criar um teste automatizado é bom que cobre um caso de uso além do seu teste de usuário. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app-modules/panel-app/src/Pages/ProfilePage.php`:
- Line 138: Update the form field configuration containing ->live() to use
live(onBlur: true), so the client-side JsContent counter updates without sending
a network request on every keystroke while preserving live synchronization when
the field loses focus.
In `@app-modules/panel-app/tests/Feature/ProfilePageTest.php`:
- Around line 193-199: Update the form field setup exercised by ProfilePageTest
so the about Textarea uses live(onBlur: true), and change the assertion in the
assertFormFieldExists callback to expect the ['blur'] state binding modifier.
Add automated coverage in ProfilePageTest verifying the field’s emoji behavior
while preserving the existing hint assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 852504c4-2461-4354-a96e-d70682907f38
📒 Files selected for processing (2)
app-modules/panel-app/src/Pages/ProfilePage.phpapp-modules/panel-app/tests/Feature/ProfilePageTest.php
| 'about' => 'abc', | ||
| ]) | ||
| ->assertFormFieldExists('about', function (Textarea $field): bool { | ||
| expect($field->getHint())->toBeInstanceOf(JsContent::class) | ||
| ->and($field->getHint()->toHtml())->toContain('Array.from($state') | ||
| ->toContain('500') | ||
| ->and($field->getStateBindingModifiers())->toBe(['live']); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add emoji test coverage and update state binding modifier.
If ->live(onBlur: true) is applied to the field, the expected binding modifier must be updated to ['blur']. As per PR objectives, the emoji behavior should also be covered by an automated test.
♻️ Proposed fix
- 'about' => 'abc',
+ 'about' => 'abc 🚀👨💻',
])
->assertFormFieldExists('about', function (Textarea $field): bool {
expect($field->getHint())->toBeInstanceOf(JsContent::class)
->and($field->getHint()->toHtml())->toContain('Array.from($state')
->toContain('500')
- ->and($field->getStateBindingModifiers())->toBe(['live']);
+ ->and($field->getStateBindingModifiers())->toBe(['blur']);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'about' => 'abc', | |
| ]) | |
| ->assertFormFieldExists('about', function (Textarea $field): bool { | |
| expect($field->getHint())->toBeInstanceOf(JsContent::class) | |
| ->and($field->getHint()->toHtml())->toContain('Array.from($state') | |
| ->toContain('500') | |
| ->and($field->getStateBindingModifiers())->toBe(['live']); | |
| 'about' => 'abc 🚀👨💻', | |
| ]) | |
| ->assertFormFieldExists('about', function (Textarea $field): bool { | |
| expect($field->getHint())->toBeInstanceOf(JsContent::class) | |
| ->and($field->getHint()->toHtml())->toContain('Array.from($state') | |
| ->toContain('500') | |
| ->and($field->getStateBindingModifiers())->toBe(['blur']); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app-modules/panel-app/tests/Feature/ProfilePageTest.php` around lines 193 -
199, Update the form field setup exercised by ProfilePageTest so the about
Textarea uses live(onBlur: true), and change the assertion in the
assertFormFieldExists callback to expect the ['blur'] state binding modifier.
Add automated coverage in ProfilePageTest verifying the field’s emoji behavior
while preserving the existing hint assertions.
Ajustei a expressão para usar |
|
Atualizei novamente seguindo os últimos comentários:
Rodei: vendor\bin\pest app-modules\panel-app\tests\Feature\ProfilePageTest.php --compact |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app-modules/panel-app/src/Pages/ProfilePage.php`:
- Around line 135-138: Update the counter expression in the profile text field
to measure UTF-16 code units, matching the 500-character maxLength enforcement,
instead of using Array.from(...).length. Preserve the existing null-safe state
handling and 500-character display limit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 85b9d964-dbd8-4878-b992-1efc93e62d24
📒 Files selected for processing (2)
app-modules/panel-app/src/Pages/ProfilePage.phpapp-modules/panel-app/tests/Feature/ProfilePageTest.php
| ->hint(JsContent::make('`${Array.from($state ?? "").length}/500`')) | ||
| ->maxLength(500) | ||
| ->rows(4) | ||
| ->live(onBlur: true) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app-modules/panel-app/src/Pages/ProfilePage.php"
echo "== file size =="
wc -l "$FILE"
echo
echo "== relevant lines =="
sed -n '120,150p' "$FILE" | cat -nRepository: he4rt/heartdevs.com
Length of output: 2210
🌐 Web query:
HTML maxlength UTF-16 code units textarea maxlength semantics MDN
💡 Result:
The HTML maxlength attribute for textarea elements defines the maximum length of the element's value, measured in UTF-16 code units [1][2]. Key semantic details include: Measurement Unit: The limit is specifically defined by the number of UTF-16 code units, not necessarily the number of user-perceived characters (grapheme clusters) [1]. Since many common characters fit into a single 16-bit code unit, this often matches the character count, but characters outside the Basic Multilingual Plane (such as certain emojis or complex symbols) require two UTF-16 code units (a surrogate pair), meaning they count as two against the maxlength limit [1][3]. Newline Normalization: For textarea elements, the length calculation includes a normalization step where newline characters are counted as a single character, regardless of whether the platform uses CR, LF, or CRLF line endings [4][5]. Constraint Validation: The maxlength constraint is evaluated only when the value is modified by the user [1][6]. If the text length exceeds the specified limit, the element is considered invalid, and the ValidityState object's tooLong property will be true [1][7]. Browsers typically prevent users from entering text beyond the specified limit, but this constraint is not applied when values are set programmatically (e.g., via JavaScript) [1][5]. API Properties: - The HTMLTextAreaElement.maxLength property reflects the maxlength attribute, returning the limit or -1 if no limit exists [7]. - The HTMLTextAreaElement.textLength property provides the current length of the textarea's value in UTF-16 code units [8].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/maxlength
- 2: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/textarea
- 3: Be more precise about what max/minlength measures mdn/content#27659
- 4: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
- 5: https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
- 6: https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement
- 7: https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/maxLength
- 8: https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/textLength
Keep the counter on UTF-16 units. Array.from($state ?? "").length counts code points, but maxLength(500) is enforced in UTF-16 units, so emoji/astral characters will show a smaller count than the actual limit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app-modules/panel-app/src/Pages/ProfilePage.php` around lines 135 - 138,
Update the counter expression in the profile text field to measure UTF-16 code
units, matching the 500-character maxLength enforcement, instead of using
Array.from(...).length. Preserve the existing null-safe state handling and
500-character display limit.
O que foi feito
Adiciona um contador de caracteres no campo Sobre da página de perfil, exibindo o progresso no formato
atual/500.A mudança mantém o limite já existente de 500 caracteres e apenas melhora o feedback visual para o usuário durante o preenchimento do perfil.
Detalhes técnicos
Textarea::make('about').maxLength(500).live(onBlur: true)porlive()para atualizar o campo durante a digitação.mb_strlen()para respeitar caracteres multibyte.Evidência visual
Testes
Teste manual
atual/500.Closes #426