Skip to content

Complete Notifications correctness and Slack parity - #472

Merged
binaryfire merged 19 commits into
0.4from
audit/notifications-correctness-parity
Aug 5, 2026
Merged

Complete Notifications correctness and Slack parity#472
binaryfire merged 19 commits into
0.4from
audit/notifications-correctness-parity

Conversation

@binaryfire

@binaryfire binaryfire commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

This completes the Notifications audit and brings the package up to date with the supported Laravel framework and Slack notification-channel APIs. It adds notification mail attachments from storage, current Slack Block Kit selects and Builder URL support, modern webhook payloads, and the missing queue and database notification coverage.

It also fixes several correctness issues: nested channel failures now preserve exact event ownership, generated Slack action IDs remain valid, truncated Slack text remains valid UTF-8, anonymous notifiables have an explicit null identity, and database notification relationships and scopes carry accurate types.

For more details, see: docs/plans/2026-08-04-2030-notifications-correctness-slack-parity-and-reentrant-failure-ownership.md.

What changed

  • Added MailMessage::attachFromStorage() and attachFromStorageDisk() with default disk, named disk, display name, and MIME support.
  • Added static and users Slack selects with placeholders, focus state, initial selections, and concrete fluent return types.
  • Shared and corrected automatic Slack action ID generation so empty, expanding, and non-transliterable seeds produce usable values within Slack's limit.
  • Made over-limit Slack text truncation UTF-8 safe without changing existing byte-limit behavior.
  • Added SlackMessage::toBlockKitBuilderUrl() and made dd() delegate to it.
  • Allowed Slack webhooks to deliver both modern Block Kit messages and retained legacy attachment messages.
  • Preserved Horizon's intentional webhook and Web API message representations with direct coverage.
  • Made NotificationFailed ownership safe for nested and concurrent channel attempts using coroutine-local save and restore semantics.
  • Removed the redundant ChannelManager singleton binding while retaining identical Dispatcher and Factory alias resolution.
  • Completed queue precedence, database read-state, relationship generic, scope return, clone, and anonymous identity behavior.
  • Corrected package dependencies, upstream provenance, source-to-test mapping, framework test bases, and process-global test fixture cleanup.

Correctness and compatibility

No supported Laravel Notifications API was removed. The public changes are additive except where existing generated Slack values or nested failure handling were demonstrably incorrect.

The failure marker exists only during a notification channel attempt and restores the exact prior coroutine state. External failure events do not create sender state, nested sends cannot suppress their parent, and sibling coroutines cannot observe one another's delivery configuration.

Slack delivery remains direct and unpooled. Legacy attachment messages remain supported alongside modern Block Kit messages.

Performance

Successful channel delivery adds only constant-time coroutine-context reads and writes around the existing transport call. It adds no lock, I/O, retry, reflection, registry, retained allocation, or request-wide work. UTF-8 truncation runs only after the existing over-limit check.

Validation

  • composer fix
  • Focused Notifications unit and integration suites
  • Queue missing-model notification coverage
  • Horizon notification representation coverage
  • Eloquent type fixtures and package metadata checks
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Added storage-based mail attachments, including disk selection, custom filenames, and MIME types.
    • Added Slack static-select and user-select controls with fluent configuration.
    • Added Slack Block Kit Builder preview URL generation.
    • Added support for modern Slack messages alongside legacy webhook payloads.
  • Bug Fixes

    • Improved UTF-8 character-limit handling and invalid encoding validation across Slack content.
    • Improved notification failure-state isolation during nested and concurrent delivery attempts.
  • Documentation

    • Updated notification and Slack guidance with current attachment, interaction, and preview examples.

Expose attachFromStorage and attachFromStorageDisk directly on MailMessage while preserving Laravel method order, named arguments, display names, MIME options, and default-disk behavior.

Cover default and named disks, basename fallback, custom names, MIME metadata, and real attachment contents through an isolated framework filesystem fixture.
Move automatic action ID generation into a shared trait, preserve ordinary IDs, replace unusable empty slugs, and cap the final generated value at Slack’s 255-byte protocol limit.

Add counterfactual coverage for expanding slugs, non-transliterable labels, explicit overlong IDs, and unchanged valid button identifiers.
Port static and users select accessories with placeholders, focus state, initial choices, explicit IDs, and concrete fluent return types. Wire both select types into ActionsBlock using deterministic text-derived identifiers.

Normalize supported scalar option values once, reject values that collapse to an unusable empty Slack identifier, and cover serialization, chaining, defaults, and unknown-option failures.
Use mb_strcut only after Slack text exceeds its existing byte limit so truncation cannot split a multibyte character while unchanged ASCII and in-limit text keep their current behavior.

Declare the required extension at the split-package boundary, remove unused Filesystem and Object Pool dependencies, and enforce the resulting dependency and provider metadata against the monorepo manifest.
Expose the generated Slack Block Kit Builder URL without terminating execution and make dd delegate to that public representation while retaining raw-payload dumping.

Move SlackMessage coverage beside its source namespace, extract only the genuinely shared channel fixtures and test setup, and restore direct ImageElement coverage so future upstream changes retain a clear source-to-test mapping.
Allow incoming webhooks to serialize both modern Block Kit messages and retained legacy attachment messages without converting either representation or duplicating the delivery channel.

Cover string and PSR URI webhooks, Web API routes, route short-circuiting, and Horizon’s intentional legacy-webhook versus modern-Web-API payload selection.
Save and restore the exact coroutine-local failure marker around each channel attempt so nested sends cannot suppress or duplicate an outer NotificationFailed event. The process-global dispatcher listener now marks only an active sender-owned attempt.

Exercise the real provider listener across nested success and failure, sequential attempts, cleanup, external events, and sibling coroutines. Remove the redundant ChannelManager singleton registration while proving both public aliases resolve the same auto-singleton.
Interleave sibling coroutines after assigning different delivery channels and locales, then verify each observes only its own state and a fresh context returns to the worker defaults.

This pins the existing coroutine-local manager contract without adding production state, locks, cleanup hooks, or request-wide work.
Use strict queue-interface membership when annotating notification mail, type queued-notification cloning explicitly, and retain the established object ownership during clone.

Port current queue-precedence regressions proving runtime onQueue selection and constructor values override queue attributes while attributes remain the fallback when no explicit queue is chosen.
Give notification relationships precise model generics and make read and unread scopes return the supplied Eloquent builder explicitly across the query-builder forwarding boundary.

Cover real read/unread selection through builder dispatch, frozen initial read state, all relationship types, and existing UUID-backed notification behavior without changing the public predicate or scope APIs.
Return null explicitly from the mixed getKey contract so anonymous notification fakes, broadcast naming, and other identity consumers cannot hit an implicit-null TypeError.

Cover the public identity result and keep anonymous route accumulation class-owned across mail, multiple-route, and fake integration paths.
Move the remaining notification unit coverage onto Hypervel’s coroutine-aware base test case and complete native test and fixture method typing.

Preserve the existing behavioral assertions across mail integration, broadcast messages, actions, simple messages, and Slack blocks while removing raw PHPUnit lifecycle gaps.
Own both missing-translation probe globals in class setup and teardown so reverse ordering and prior failures cannot leak process-global state into later integration tests.

This is a test-lifecycle correction only; Translation source behavior and its later package audit remain unchanged.
Document notification mail attachments from default and named filesystem disks, Slack static and users selects, and non-terminating Block Kit Builder URL retrieval in the primary guide.

Keep the package README minimal while linking the user documentation and both upstream projects that Notifications intentionally tracks.
Record the need for first-class PHPStan extensions that preserve Eloquent builder model types through forwarded query methods and expose named scopes on builders and relations.

Use Larastan as prior art and avoid runtime wrappers, duplicate method inventories, or wider source types whose only purpose would be satisfying static analysis.
Record the verified upstream baselines, accepted findings, rejected speculative concerns, architecture boundaries, complete implementation shape, and regression strategy for the Notifications audit.

Carry the core anti-overengineering and performance-tradeoff rules verbatim so the work remains correctly constrained after context restoration without duplicating decision history.
Mark Notifications complete, normalize the shared finding records, and record the final correctness, parity, coroutine-lifecycle, performance, regression, and API assessment.

Close the Contracts, Queue, Eloquent, Support, Macroable, and Horizon revalidation edges; attribute the Translation fixture correction; restore the routing index to None; and keep the package checklist synchronized with the audited package set.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10e07261-878e-435f-852c-e7e9fdf2d0fe

📥 Commits

Reviewing files that changed from the base of the PR and between 56cde36 and cdb61df.

📒 Files selected for processing (29)
  • docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
  • docs/plans/2026-08-04-2030-notifications-correctness-slack-parity-and-reentrant-failure-ownership.md
  • src/boost/docs/notifications.md
  • src/notifications/README.md
  • src/notifications/src/Slack/BlockKit/Blocks/ActionsBlock.php
  • src/notifications/src/Slack/BlockKit/Blocks/ContextBlock.php
  • src/notifications/src/Slack/BlockKit/Blocks/DividerBlock.php
  • src/notifications/src/Slack/BlockKit/Blocks/HeaderBlock.php
  • src/notifications/src/Slack/BlockKit/Blocks/ImageBlock.php
  • src/notifications/src/Slack/BlockKit/Blocks/SectionBlock.php
  • src/notifications/src/Slack/BlockKit/Composites/PlainTextOnlyTextObject.php
  • src/notifications/src/Slack/BlockKit/Elements/ButtonElement.php
  • src/notifications/src/Slack/BlockKit/Elements/ImageElement.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/SelectElement.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/SelectOption.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/StaticSelectElement.php
  • src/notifications/src/Slack/SlackMessage.php
  • tests/Notifications/Slack/Blocks/ActionsBlockTest.php
  • tests/Notifications/Slack/Blocks/ContextBlockTest.php
  • tests/Notifications/Slack/Blocks/DividerBlockTest.php
  • tests/Notifications/Slack/Blocks/HeaderBlockTest.php
  • tests/Notifications/Slack/Blocks/ImageBlockTest.php
  • tests/Notifications/Slack/Blocks/SectionBlockTest.php
  • tests/Notifications/Slack/Composites/PlainTextOnlyTextObjectTest.php
  • tests/Notifications/Slack/Elements/ButtonElementTest.php
  • tests/Notifications/Slack/Elements/ImageElementTest.php
  • tests/Notifications/Slack/Elements/Selects/SelectOptionTest.php
  • tests/Notifications/Slack/Elements/Selects/StaticSelectElementTest.php
  • tests/Notifications/Slack/SlackMessageTest.php
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/notifications/src/Slack/BlockKit/Elements/Selects/SelectElement.php
  • tests/Notifications/Slack/Blocks/ContextBlockTest.php
  • src/notifications/src/Slack/BlockKit/Elements/ButtonElement.php
  • src/notifications/src/Slack/BlockKit/Blocks/ActionsBlock.php
  • src/boost/docs/notifications.md
  • tests/Notifications/Slack/Composites/PlainTextOnlyTextObjectTest.php
  • src/notifications/src/Slack/SlackMessage.php
  • tests/Notifications/Slack/SlackMessageTest.php
  • docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
  • tests/Notifications/Slack/Blocks/ActionsBlockTest.php

📝 Walkthrough

Walkthrough

The Notifications package adds storage attachments, Slack Block Kit selectors, UTF-8 validation, webhook compatibility, Block Kit Builder URLs, coroutine-local failure handling, stronger typing, expanded tests, and audit documentation.

Changes

Notifications correctness and audit completion

Layer / File(s) Summary
Mail attachments and database contracts
src/notifications/src/Messages/MailMessage.php, src/notifications/src/DatabaseNotification.php, src/notifications/src/HasDatabaseNotifications.php, src/notifications/composer.json, tests/Notifications/NotificationMailMessageTest.php, tests/Integration/Notifications/DatabaseNotificationTest.php
Adds storage-disk mail attachments, generic database relationships, explicit scope returns, and package metadata updates.
Slack Block Kit and payload compatibility
src/notifications/src/Slack/..., src/notifications/src/Channels/SlackWebhookChannel.php, src/boost/docs/notifications.md, tests/Notifications/Slack/..., tests/Notifications/NotificationSlackChannelTest.php
Adds select elements, generated IDs, UTF-8-aware limits, modern and legacy webhook payloads, Builder URLs, and related tests and documentation.
Failure state and manager lifecycle
src/notifications/src/NotificationSender.php, src/notifications/src/NotificationServiceProvider.php, tests/Integration/Notifications/NotificationFailedEventTest.php, tests/Notifications/CoroutineIsolationTest.php
Scopes failure markers to active channel attempts, restores coroutine context, guards external failure events, and validates manager aliases and isolation.
Queue, routing, and notification validation
tests/Notifications/NotificationSenderTest.php, tests/Notifications/SlackWebApiChannelTest.php, tests/Notifications/SlackNotificationRouterChannelTest.php, tests/Notifications/*
Adds queue attribute precedence, Slack route resolution, Horizon payload, database state, lifecycle cleanup, and explicit test typing coverage.
Audit records and documentation
docs/plans/*, docs/todo.md, src/notifications/README.md
Records implementation findings, cross-package revalidation, completion status, package documentation, and the remaining PHPStan Eloquent extension task.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NotificationSender
  participant CoroutineContext
  participant NotificationFailed
  participant SlackWebhookChannel
  participant SlackMessage
  NotificationSender->>CoroutineContext: initialize attempt failure state
  NotificationSender->>SlackWebhookChannel: send notification
  SlackWebhookChannel->>SlackMessage: serialize modern or legacy payload
  SlackWebhookChannel->>NotificationFailed: dispatch failure event on error
  NotificationFailed->>CoroutineContext: mark active attempt
  NotificationSender->>CoroutineContext: restore prior state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary Notifications correctness fixes and Slack API parity changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/notifications-correctness-parity

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.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR completes the Notifications audit with current mail-storage attachment APIs, Slack Block Kit and webhook support, coroutine-local failure ownership, and corrected queue and database notification behavior.

  • Preserves exact Slack option identities and adds static/users select support, bounded action IDs, UTF-8-safe text handling, and Block Kit Builder URLs.
  • Supports both modern Block Kit and legacy attachment webhook payloads.
  • Adds reentrant, coroutine-isolated notification failure ownership.
  • Expands queue, database, package metadata, type, and integration coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported static-select identity mismatch is fixed because registration, serialization, and initial-option lookup now use the same exact value.

Important Files Changed

Filename Overview
src/notifications/src/Slack/BlockKit/Elements/Selects/StaticSelectElement.php Uses exact option values consistently for registration and initial selection while enforcing static-select cardinality.
src/notifications/src/Slack/BlockKit/Elements/Selects/SelectOption.php Preserves option values verbatim and validates nonempty, character-bounded interaction identities.
src/notifications/src/NotificationSender.php Adds save-and-restore semantics for coroutine-local nested channel failure ownership.
src/notifications/src/Channels/SlackWebhookChannel.php Extends webhook delivery to modern Block Kit payloads while retaining legacy attachments.
src/notifications/src/Messages/MailMessage.php Adds storage-backed attachment APIs with disk, display-name, and MIME support.
src/notifications/src/DatabaseNotification.php Corrects database notification read-state, relationship, and query-scope contracts.

Reviews (2): Last reviewed commit: "Harden Slack Block Kit protocol boundari..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@src/notifications/src/Slack/BlockKit/Elements/Selects/SelectElement.php`:
- Around line 58-60: Update SelectElement::placeholder() to construct
PlainTextOnlyTextObject with the Slack-specific 150-character limit instead of
the generic default, and add a boundary test confirming 150 characters are
accepted while longer placeholders are rejected.

In `@src/notifications/src/Slack/BlockKit/Elements/Selects/SelectOption.php`:
- Around line 45-54: The value method must reject normalized option values
exceeding Slack’s 150-character limit. Add a length check for $normalizedValue
before assigning it to $this->value, and add a regression test covering a
151-character normalized value.

In
`@src/notifications/src/Slack/BlockKit/Elements/Selects/StaticSelectElement.php`:
- Around line 37-73: Validate the option count in StaticSelectElement: update
addOption() to throw when adding a 101st unique value while allowing replacement
of an existing value, and update toArray() to reject serialization when no
options exist instead of emitting an empty options array. Adjust the
static-select tests covering empty-option serialization to expect the new
validation behavior.

In `@src/notifications/src/Slack/SlackMessage.php`:
- Around line 330-332: Update toBlockKitBuilderUrl() to encode the filtered
message data with the JSON option that throws JsonException on failures,
ensuring rawurlencode() receives a valid string. Add coverage for invalid UTF-8
in a Block Kit text value and assert that JsonException is raised.

In `@tests/Notifications/Slack/Blocks/ActionsBlockTest.php`:
- Around line 121-138: Update StaticSelectElement::toArray() to reject empty
option lists and accept only one through 100 options, enforcing Slack’s required
bounds. Modify the ActionsBlock test to add at least one option to the static
select before asserting the serialized payload, including that option in the
expected array.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0f451ce-ab2c-4ded-aa3e-ebba66c85a01

📥 Commits

Reviewing files that changed from the base of the PR and between 9904752 and 56cde36.

📒 Files selected for processing (67)
  • docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
  • docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
  • docs/plans/2026-08-04-2030-notifications-correctness-slack-parity-and-reentrant-failure-ownership.md
  • docs/todo.md
  • src/boost/docs/grpc.md
  • src/boost/docs/notifications.md
  • src/grpc/src/Metadata.php
  • src/notifications/README.md
  • src/notifications/composer.json
  • src/notifications/src/AnonymousNotifiable.php
  • src/notifications/src/Channels/MailChannel.php
  • src/notifications/src/Channels/SlackWebhookChannel.php
  • src/notifications/src/DatabaseNotification.php
  • src/notifications/src/HasDatabaseNotifications.php
  • src/notifications/src/Messages/MailMessage.php
  • src/notifications/src/NotificationSender.php
  • src/notifications/src/NotificationServiceProvider.php
  • src/notifications/src/SendQueuedNotifications.php
  • src/notifications/src/Slack/BlockKit/Blocks/ActionsBlock.php
  • src/notifications/src/Slack/BlockKit/Composites/PlainTextOnlyTextObject.php
  • src/notifications/src/Slack/BlockKit/Elements/ButtonElement.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/SelectElement.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/SelectOption.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/StaticSelectElement.php
  • src/notifications/src/Slack/BlockKit/Elements/Selects/UsersSelectElement.php
  • src/notifications/src/Slack/BlockKit/Elements/Traits/GeneratesDefaultIds.php
  • src/notifications/src/Slack/Contracts/AccessoryContract.php
  • src/notifications/src/Slack/SlackMessage.php
  • tests/Horizon/Notifications/LongWaitDetectedTest.php
  • tests/Integration/Notifications/DatabaseNotificationTest.php
  • tests/Integration/Notifications/NotificationFailedEventTest.php
  • tests/Integration/Notifications/SendingMailNotificationsTest.php
  • tests/Integration/Notifications/SendingMailableNotificationsTest.php
  • tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php
  • tests/Integration/Translation/TranslatorTest.php
  • tests/Notifications/CoroutineIsolationTest.php
  • tests/Notifications/NotificationActionTest.php
  • tests/Notifications/NotificationBroadcastChannelTest.php
  • tests/Notifications/NotificationChannelManagerTest.php
  • tests/Notifications/NotificationDatabaseChannelTest.php
  • tests/Notifications/NotificationMailMessageTest.php
  • tests/Notifications/NotificationMessageTest.php
  • tests/Notifications/NotificationRoutesNotificationsTest.php
  • tests/Notifications/NotificationSenderTest.php
  • tests/Notifications/NotificationSlackChannelTest.php
  • tests/Notifications/PackageMetadataTest.php
  • tests/Notifications/Slack/Blocks/ActionsBlockTest.php
  • tests/Notifications/Slack/Blocks/ContextBlockTest.php
  • tests/Notifications/Slack/Blocks/DividerBlockTest.php
  • tests/Notifications/Slack/Blocks/HeaderBlockTest.php
  • tests/Notifications/Slack/Blocks/ImageBlockTest.php
  • tests/Notifications/Slack/Blocks/SectionBlockTest.php
  • tests/Notifications/Slack/Composites/ConfirmObjectTest.php
  • tests/Notifications/Slack/Composites/PlainTextOnlyTextObjectTest.php
  • tests/Notifications/Slack/Composites/TextObjectTest.php
  • tests/Notifications/Slack/Elements/ButtonElementTest.php
  • tests/Notifications/Slack/Elements/ImageElementTest.php
  • tests/Notifications/Slack/Elements/Selects/SelectOptionTest.php
  • tests/Notifications/Slack/Elements/Selects/StaticSelectElementTest.php
  • tests/Notifications/Slack/Elements/Selects/UsersSelectElementTest.php
  • tests/Notifications/Slack/Fixtures/SlackChannelTestNotifiable.php
  • tests/Notifications/Slack/Fixtures/SlackChannelTestNotification.php
  • tests/Notifications/Slack/SlackMessageTest.php
  • tests/Notifications/Slack/SlackWebApiChannelTest.php
  • tests/Notifications/Slack/TestCase.php
  • tests/Notifications/SlackNotificationRouterChannelTest.php
  • types/Database/Eloquent/Model.php

Comment thread src/notifications/src/Slack/BlockKit/Elements/Selects/SelectElement.php Outdated
Comment thread src/notifications/src/Slack/BlockKit/Elements/Selects/SelectOption.php Outdated
Comment thread src/notifications/src/Slack/SlackMessage.php
Comment thread tests/Notifications/Slack/Blocks/ActionsBlockTest.php
Preserve static-select option values as exact interaction identities and enforce Slack's documented option, placeholder, cardinality, image, action, and block limits using character counts.

Make text truncation UTF-8 safe, surface Block Kit Builder JSON failures as JsonException, and route image-block constructor alt text through the existing invariant owner.

Add counterfactual coverage for exact identities, select bounds, multibyte limits, malformed text, image construction, and JSON encoding. Update the public Laravel differences, guide example, implementation plan, and audit ledger to match the final behavior.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai @greptile-apps Thanks for the review. I addressed the concrete findings in cdb61df9f.

  • Slack select option values now remain exact interaction identities instead of being normalized.
  • Static selects enforce one through 100 options, option values and placeholders enforce their published limits, and existing Block Kit limits count characters rather than bytes.
  • Block Kit Builder JSON failures now surface as JsonException.
  • Image-block construction uses the existing alt-text validator, and image elements enforce the published URL limit without inventing an alt-text cap.
  • The tests cover the failure cases counterfactually, including multibyte boundaries, identity collisions, malformed UTF-8, and the deliberate image-element distinction.
  • The public Laravel differences and audit records now match the shipped behavior.

I did not add blanket docstrings for the coverage warning. This repository uses targeted Laravel-style docblocks where they add contract or behavioral information, not percentage-driven comments.

The complete composer fix gate is green.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git show --no-ext-diff --format=fuller --stat cdb61df9f
printf '\n--- Changed files ---\n'
git diff-tree --no-commit-id --name-only -r cdb61df9f

printf '\n--- Relevant implementation and test diff ---\n'
git show --no-ext-diff --format= --find-renames --find-copies cdb61df9f -- \
  src/notifications/src/Slack \
  tests/Notifications/Slack \
  docs/plans \
  | sed -n '1,1600p'

Length of output: 64007


@binaryfire Verified cdb61df9f.

The commit addresses the concrete Slack Block Kit findings:

  • SelectOption preserves exact option identities and enforces empty and 150-character boundaries.
  • StaticSelectElement enforces one through 100 options.
  • Block Kit limits use UTF-8 character counts.
  • Over-limit malformed text fails explicitly. Valid multibyte text truncates safely.
  • toBlockKitBuilderUrl() now exposes JSON encoding failures as JsonException.
  • ImageBlock constructor validation now uses the existing alternative-text invariant.
  • ImageElement enforces the URL limit without adding an unsupported alternative-text cap.
  • Focused regression tests cover the reported counterfactual cases.

The targeted-docblock approach is appropriate. No blanket coverage-driven documentation is needed.

🐇 ✅


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

The fixes in cdb61df9f are correct and complete. Each finding has been addressed precisely:

  • Option identity preservationSelectOption::value() casts to string without any normalization, and StaticSelectElement keys the options map by raw value so 'A B' and 'ab' remain distinct through to the payload. The counterfactual test directly asserts both the options array and initial_option carry the unmodified value.

  • Cardinality and limit enforcement — minimum-one and maximum-100 are checked lazily at toArray(), which is the right place (construction is valid, serialization is where protocol constraints apply). The replacement-at-the-limit test correctly demonstrates value-keyed deduplication rather than unchecked appending.

  • Character vs. byte countsPlainTextOnlyTextObject, SelectOption::value(), and ImageElement all use mb_strlen(..., 'UTF-8'), so the str_repeat('你', 150) boundary tests actually exercise the intended limit.

  • JSON_THROW_ON_ERROR in toBlockKitBuilderUrl() — the malformed UTF-8 test via a metadata payload with "\xFF" is a clean, self-contained way to trigger the encoding failure without a mock.

  • ImageElement alt-text handling — URL validated in constructor, alt-text deferred to toArray() as a LogicException. The distinction from ImageBlock (which validates alt-text at construction via PlainTextOnlyTextObject) is deliberate and the element test covers it explicitly.

  • Docblock policy — the targeted approach is consistent with the rest of the codebase and is the right call.

The implementation is clean. No further changes needed from my side.

@binaryfire
binaryfire merged commit ca4cffe into 0.4 Aug 5, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant