Skip to content

Release v0.1.0-mvp#297

Merged
menvil merged 70 commits into
mainfrom
release/v0.1.0-mvp
Jun 8, 2026
Merged

Release v0.1.0-mvp#297
menvil merged 70 commits into
mainfrom
release/v0.1.0-mvp

Conversation

@menvil

@menvil menvil commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Completes Phase 25 (Hardening: domain exceptions, UI/API error mapping, lifecycle logging, rate limiting, MVP happy path test)
  • Completes Phase 26 (Release Preparation: scope audit, documentation, seeders, quality gate)

Supported Conversions

PNG → JPG, PNG → WEBP, PNG → PDF, JPG → PNG, JPG → WEBP, JPG → PDF

Quality Gate

Check Result
composer test ✅ 767 tests, 766 passed
composer lint ✅ Pass
npm run build ✅ Pass
php artisan migrate:fresh --seed ✅ Pass

See docs/release/quality-gate-result.md for full details.

Known Limitations

  • Web rate limiting registered but not wired to Livewire action level
  • No video/audio/office conversion
  • No OCR, batch conversion, or WebSockets
  • No Sentry/Bugsnag/OpenTelemetry

Full list: docs/release/mvp-scope-audit.md

Deployment

See docs/deployment/production-checklist.md before deploying.

🤖 Generated with Claude Code


Summary by cubic

Release v0.1.0 MVP with hardened error handling, lifecycle logging, rate limiting, and polished docs. Adds standardized JSON for unmapped API HttpExceptions (now preserving headers), corrects supported-conversion options, and seeds demo results with real stub files for download.

  • New Features

    • Domain exception system with stable codes and details; UI/API mappers return clear, consistent messages.
    • Conversion and billing lifecycle logging via ConversionLogger and BillingLogger.
    • Rate limiting for web-upload, web-conversion-create, and api-v1.
    • Expired conversion results return 410 on web and a structured API error.
    • Demo seeders: demo@example.com with credits and sample conversion history.
    • Supported conversions: PNG ↔ JPG/WEBP, PNG → PDF, JPG → PDF.
    • New docs: README install guide, production checklist, queue worker, scheduler, storage/permissions, Stripe webhooks, local development.
  • Bug Fixes

    • Prevent logging failures from flipping a successful conversion to failed (wrap job completion logging; guard billing logs).
    • API HttpExceptions preserve status codes and headers and return standardized JSON { error.code: 'http_error' } instead of HTML.
    • Demo seeders only run in local/dev/staging; demo conversions now reference a result_file_id and write stub files so downloads work.
    • Consistent domain model: InsufficientCreditsException and UnsupportedConversionException extend DomainException.
    • Docs and env cleanup: PHP 8.3, correct scheduler command (hourly files:cleanup-expired, --sync example), converter keys (png:jpg), JPG→PNG options, quality values, supported conversion options matrix (PNG→WEBP adds remove_metadata; PNG/JPG→PDF add fit_mode and compression; fit_mode values are contain|cover|original), API docs URL, and remove unused env var.

Written for commit 710b9ae. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Structured lifecycle logging for conversions and billing; demo seeders and an end-to-end MVP happy-path test
    • New user-facing UI error mapper and standardized API error rendering; improved expired-result handling (HTTP 410)
  • Documentation

    • New project README plus comprehensive deployment, development, release, and operational docs
    • Reorganized environment template
  • Refactor

    • Unified domain exception contract with stable codes and structured details; updated API/UI error mappings
  • Tests

    • Broad test additions covering errors, logging, rate limits, seeders, and converters

menvil added 30 commits June 5, 2026 19:11
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Need an answer fast? Review this PR in Change Stack to ask focused questions about the PR or a changed range.

Review Change Stack

Warning

Review limit reached

@menvil, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 36a861e0-531f-4bf3-a0ce-4a984ac9db42

📥 Commits

Reviewing files that changed from the base of the PR and between 9f4c6c2 and 710b9ae.

📒 Files selected for processing (2)
  • bootstrap/app.php
  • docs/release/supported-conversions.md
📝 Walkthrough

Walkthrough

Adds a DomainException contract and concrete domain exceptions, structured API/UI error mappings, billing and conversion loggers, logging and exception wiring in actions/jobs/controllers, idempotent demo seeders, extensive docs and README rewrite, plus unit and feature tests covering exceptions, logging, mapping, seeders, rate limits, and an MVP happy-path.

Changes

Complete PR checkpoint

Layer / File(s) Summary
Domain exception contract and base
app/Exceptions/DomainExceptionContract.php, app/Exceptions/DomainException.php
Defines DomainExceptionContract (code(): string, details(): array) and an abstract DomainException base returning empty details().
Concrete domain exceptions
app/Exceptions/Conversions/*, app/Exceptions/Billing/InsufficientCreditsException.php, app/Exceptions/Features/FeatureNotAvailableException.php, app/Exceptions/Files/FileTooLargeException.php, app/Exceptions/Storage/StorageLimitExceededException.php
Adds/refactors many exceptions to implement the contract, store structured metadata, and expose code()/details() with stable factory constructors (e.g., forJob, forConversion, forCost, forLimit).
Converter/format exceptions & normalization
app/Support/Converters/Exceptions/*, app/Support/Conversions/Exceptions/UnsupportedConversionException.php, app/Enums/FileFormat.php
Refactors converter/format exceptions to implement DomainExceptionContract, adds forFormat()/forPair() factories, deprecates old factories, and updates FileFormat::normalize() to use forFormat().
API exception-to-error mapping & UI mapper
app/Support/Api/ApiExceptionMapper.php, app/Support/UI/UiErrorMessage.php, app/Support/UI/UiDomainErrorMapper.php
Maps new domain exceptions into MappedApiError with code, status, and structured details; adds UI mapper producing UiErrorMessage objects with optional CTA metadata.
Structured logging components
app/Support/Logging/BillingLogger.php, app/Support/Logging/ConversionLogger.php
Introduces BillingLogger and ConversionLogger emitting structured log entries for billing and conversion lifecycle events.
Runtime integration: actions, jobs, services, controllers, bootstrap, provider
app/Actions/Conversions/CreateConversionJobAction.php, app/Jobs/ProcessConversionJob.php, app/Services/Billing/DatabaseCreditLedger.php, app/Http/Controllers/DownloadConversionResultController.php, bootstrap/app.php, app/Providers/AppServiceProvider.php
Injects loggers into actions/jobs, emits lifecycle logs (created/started/completed/failed), switches to forCost() on credit failures, throws ConversionResultExpiredException for expired downloads, adds API catch-all renderer, and registers web-upload/web-conversion-create rate limiters.
Seeders: demo data and DatabaseSeeder wiring
database/seeders/DemoUserSeeder.php, database/seeders/DemoConversionSeeder.php, database/seeders/DatabaseSeeder.php
Adds idempotent demo user and demo conversion seeders; DatabaseSeeder delegates to them only in safe environments.
Tests: exceptions, logging, mapping, seeders, rate limits, MVP happy path
tests/Unit/Exceptions/*, tests/Unit/Support/Api/ApiExceptionMapperTest.php, tests/Unit/UI/UiDomainErrorMapperTest.php, tests/Feature/Logging/*, tests/Feature/Api/V1/*, tests/Feature/RateLimit/*, tests/Feature/Seeders/*, tests/Feature/MVP/MvpHappyPathTest.php
Adds unit and feature tests to assert exception code/details, API mapper outputs, UI mapping, Billing/Conversion logging, seeders idempotency, web/api rate-limit behavior, and an end-to-end MVP happy-path.
README, env template, and docs
README.md, .env.example, docs/**
Rewrites README for File Converter MVP, reorganizes .env.example, and adds docs for billing webhooks, queue workers, scheduler, storage/permissions, local development, hardening audit, and release checklists.
Minor test cleanups
tests/Feature/Livewire/*, tests/Feature/AccessibilitySmokeTest.php
Simplifies imports and adjusts minor test expectations (href format).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.1.0-mvp

@github-actions github-actions Bot added the release Triggers AI code review (CodeRabbit, Cubic) label Jun 5, 2026

@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: 4

🧹 Nitpick comments (15)
docs/development/local-development.md (2)

87-89: ⚡ Quick win

Add language identifier to branch naming code block.

The fenced code block should specify a language (text or plaintext):

-```
+```text
 feature/CONV-XXX-kebab-title
 ```
🤖 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 `@docs/development/local-development.md` around lines 87 - 89, Update the
fenced code block that currently contains the sample branch name (the block with
content "feature/CONV-XXX-kebab-title") to include a language identifier by
changing the opening fence from ``` to ```text (or ```plaintext) so the block is
rendered as plain text; locate the code block in
docs/development/local-development.md where the branch example appears and
modify the opening fence accordingly.

93-95: ⚡ Quick win

Add language identifier to commit format code block.

The fenced code block should specify a language (text or plaintext):

-```
+```text
 CONV-XXX: Short description in imperative mood
 ```
🤖 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 `@docs/development/local-development.md` around lines 93 - 95, Update the
fenced code block that currently contains "CONV-XXX: Short description in
imperative mood" to include a language identifier (e.g., use ```text or
```plaintext) so the block reads with a specified language for proper rendering;
locate the exact fenced block containing the literal "CONV-XXX: Short
description in imperative mood" and prepend the language token to the opening
backticks.
docs/deployment/scheduler.md (2)

36-38: ⚡ Quick win

Add language identifier to cron code block.

The fenced code block should specify a language for proper syntax highlighting:

-```
+```cron
 * * * * * cd /var/www/file-converter && php artisan schedule:run >> /dev/null 2>&1
 ```
🤖 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 `@docs/deployment/scheduler.md` around lines 36 - 38, The fenced code block
containing the cron entry should include a language identifier for proper
highlighting—update the block that wraps the line "* * * * * cd
/var/www/file-converter && php artisan schedule:run >> /dev/null 2>&1" to use
"cron" (i.e., replace the opening ``` with ```cron) so the snippet is fenced as
a cron code block.

52-54: ⚡ Quick win

Add language identifier to output code block.

The fenced code block should specify a language (text or plaintext):

-```
+```text
 0 2 * * *  conversions:cleanup-expired
 ```
🤖 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 `@docs/deployment/scheduler.md` around lines 52 - 54, Update the fenced code
block that contains the cron entry "0 2 * * *  conversions:cleanup-expired" to
include a language identifier (e.g., use ```text or ```plaintext) immediately
after the opening backticks so the block reads as a plain-text code block and
renders correctly.
README.md (2)

76-79: ⚡ Quick win

Add language identifier to code block.

The fenced code block should specify a language for proper syntax highlighting. Use text or plaintext:

-```
+```text
 Email:    demo@example.com
 Password: password
 ```
🤖 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 `@README.md` around lines 76 - 79, Update the fenced code block in README.md
that shows the demo credentials by adding a language identifier (e.g., change
``` to ```text or ```plaintext) so the block becomes fenced with a language for
proper syntax highlighting; locate the credential snippet containing "Email:   
demo@example.com" and "Password: password" and modify its opening fence
accordingly.

131-135: ⚡ Quick win

Add language identifier to code block.

The fenced code block should specify a language (env or text):

-```
+```env
 STRIPE_KEY=pk_test_...
 STRIPE_SECRET=sk_test_...
 STRIPE_WEBHOOK_SECRET=whsec_...
 ```
🤖 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 `@README.md` around lines 131 - 135, Update the fenced code block containing
the Stripe environment variables to include a language identifier (e.g., use
```env or ```text) so the block becomes ```env followed by the
STRIPE_KEY/STRIPE_SECRET/STRIPE_WEBHOOK_SECRET lines and the closing ```,
ensuring README.md's code block is properly highlighted and identified.
docs/deployment/storage-and-permissions.md (1)

9-12: ⚡ Quick win

Add language identifier to directory structure block.

The fenced code block should specify a language (text or plaintext):

-```
+```text
 storage/app/uploads/{user_id}/          — uploaded source files
 storage/app/conversions/results/{job_id}/  — conversion result files
 ```
🤖 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 `@docs/deployment/storage-and-permissions.md` around lines 9 - 12, The fenced
code block showing directory structure lacks a language identifier; update the
snippet in docs/deployment/storage-and-permissions.md (the block containing
"storage/app/uploads/{user_id}/ — uploaded source files" and
"storage/app/conversions/results/{job_id}/ — conversion result files") to
include a language tag like ```text (or ```plaintext) at the opening fence so
the block becomes a highlighted plain-text code block.
docs/billing/stripe-webhooks.md (1)

22-24: ⚡ Quick win

Add language specifier to fenced code block.

The code block should specify a language identifier for better syntax highlighting and tooling support.

📝 Suggested improvement
-```
+```http
 POST /stripe/webhook

Alternatively, use `txt` if HTTP syntax highlighting is not desired.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/billing/stripe-webhooks.md around lines 22 - 24, Update the fenced code
block that contains "POST /stripe/webhook" to include a language specifier
(e.g., add "http" or "txt" after the opening backticks) so tooling and syntax
highlighting work correctly; locate the fenced block in the docs where the
literal "POST /stripe/webhook" appears and change the opening tohttp (or

tests/Feature/RateLimit/WebRateLimitTest.php (1)

20-27: ⚡ Quick win

Exercise the named limiter through a throttled route, not direct cache hits.

Line 25/Line 27 manually hitting a handcrafted key validates cache counters, but not whether web-upload is correctly applied in request flow. This can mask real wiring/keying regressions.

Example integration-style assertion
+use Illuminate\Support\Facades\Route;
...
 it('blocks web upload after exceeding limit', function () {
     $user = User::factory()->create();

-    $key = "web-upload:{$user->id}";
-
-    // Override with a low limit for testing.
     RateLimiter::for('web-upload', fn () => Limit::perMinute(1)->by($user->id));
+    Route::post('/_test/web-upload', fn () => response()->noContent())->middleware('throttle:web-upload');

-    RateLimiter::hit($key);
-
-    expect(RateLimiter::tooManyAttempts($key, 1))->toBeTrue();
+    $this->actingAs($user)->post('/_test/web-upload')->assertNoContent();
+    $this->actingAs($user)->post('/_test/web-upload')->assertStatus(429);
 });
🤖 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 `@tests/Feature/RateLimit/WebRateLimitTest.php` around lines 20 - 27, Instead
of manipulating the RateLimiter cache directly with RateLimiter::hit($key) and
RateLimiter::tooManyAttempts($key, 1), exercise the named limiter through the
HTTP layer: keep the RateLimiter::for('web-upload', fn () =>
Limit::perMinute(1)->by($user->id)) setup, then perform two requests as the test
user against the route that uses the 'throttle:web-upload' middleware (e.g.
actingAs($user)->post('/your-upload-route') twice) and assert the second
response is a 429 (or contains the throttling response); this verifies the
middleware wiring and keying rather than only the cache counter.
tests/Feature/Logging/ConversionLifecycleLoggingTest.php (1)

23-27: ⚡ Quick win

Assert concrete context values, not only key presence.

Line 23, Line 41, and Line 60 only verify keys exist. These tests will still pass if incorrect IDs or error codes are logged, which weakens the regression signal for structured logging contracts.

Suggested test hardening
-    Log::shouldHaveReceived('info')
-        ->withArgs(fn (string $message, array $context) => $message === 'conversion.job.created'
-            && isset($context['conversion_job_id'])
-            && isset($context['user_id'])
-            && isset($context['converter_key'])
-        );
+    Log::shouldHaveReceived('info')
+        ->withArgs(fn (string $message, array $context) => $message === 'conversion.job.created'
+            && ($context['conversion_job_id'] ?? null) === $job->id
+            && ($context['user_id'] ?? null) === $user->id
+            && ($context['converter_key'] ?? null) === 'png_to_jpg'
+        );
-    Log::shouldHaveReceived('info')
-        ->withArgs(fn (string $message, array $context) => $message === 'conversion.job.completed'
-            && isset($context['conversion_job_id'])
-            && isset($context['user_id'])
-        );
+    Log::shouldHaveReceived('info')
+        ->withArgs(fn (string $message, array $context) => $message === 'conversion.job.completed'
+            && ($context['conversion_job_id'] ?? null) === $job->id
+            && ($context['user_id'] ?? null) === $user->id
+        );
-    Log::shouldHaveReceived('error')
-        ->withArgs(fn (string $message, array $context) => $message === 'conversion.job.failed'
-            && isset($context['conversion_job_id'])
-            && isset($context['user_id'])
-            && isset($context['error_code'])
-        );
+    Log::shouldHaveReceived('error')
+        ->withArgs(fn (string $message, array $context) => $message === 'conversion.job.failed'
+            && ($context['conversion_job_id'] ?? null) === $job->id
+            && ($context['user_id'] ?? null) === $user->id
+            && ($context['error_code'] ?? null) === 'driver_error'
+        );

Also applies to: 41-44, 60-64

🤖 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 `@tests/Feature/Logging/ConversionLifecycleLoggingTest.php` around lines 23 -
27, The test currently only asserts the presence of keys in the log context for
the 'conversion.job.created' expectation (the withArgs closure) and the similar
assertions at the two other occurrences; update each withArgs closure in
ConversionLifecycleLoggingTest to assert concrete expected values instead of
just isset — e.g. compare $context['conversion_job_id'] to the actual
ConversionJob id/UUID created in the test, $context['user_id'] to the test user
id, $context['converter_key'] to the expected converter key, and where
applicable assert specific error_code or status values; ensure you reference the
same message strings (like 'conversion.job.created') and the existing withArgs
closures when making these stronger assertions so the test will fail if
incorrect values are logged.
database/seeders/DemoUserSeeder.php (1)

24-25: 💤 Low value

Simplify firstOrCreate() to remove redundant user_id.

When calling firstOrCreate() on a relationship ($user->creditAccount()), the foreign key constraint is already applied by the relationship scope. Explicitly passing ['user_id' => $user->id] as the search attributes is redundant.

♻️ Simplified version
-    $user->creditAccount()->firstOrCreate(['user_id' => $user->id], ['balance' => 0]);
+    $user->creditAccount()->firstOrCreate([], ['balance' => 0]);
🤖 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 `@database/seeders/DemoUserSeeder.php` around lines 24 - 25, Remove the
redundant foreign key from the relationship call in DemoUserSeeder: instead of
passing ['user_id' => $user->id] into
$user->creditAccount()->firstOrCreate(...), call firstOrCreate with an empty
search attributes array and the create attributes only (e.g., firstOrCreate([],
['balance' => 0'])) so the relationship applies the FK scope and you still set
balance when creating the record.
tests/Feature/Seeders/DemoUserSeederTest.php (1)

18-23: ⚡ Quick win

Idempotency test should verify credit balance remains unchanged.

The test confirms the user isn't duplicated on re-run, but doesn't verify that credits aren't granted twice. The seeder's idempotency logic (line 28 of DemoUserSeeder) should prevent duplicate credit grants, but this isn't tested.

🧪 Enhanced test with credit balance verification
 it('seeder is idempotent and does not create duplicate demo user', function () {
     (new DemoUserSeeder)->run();
+    $user = User::where('email', 'demo@example.com')->first();
+    $balanceAfterFirst = app(CreditLedger::class)->balance($user);
+
     (new DemoUserSeeder)->run();
 
     expect(User::where('email', 'demo@example.com')->count())->toBe(1);
+    expect(app(CreditLedger::class)->balance($user->fresh()))->toBe($balanceAfterFirst);
 });
🤖 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 `@tests/Feature/Seeders/DemoUserSeederTest.php` around lines 18 - 23, Add an
assertion that the demo user's credit balance is unchanged after re-running the
seeder: before calling (new DemoUserSeeder)->run() the second time, load the
user (User::where('email','demo@example.com')->first()) and store their credits
(field used by DemoUserSeeder for grants), run the seeder again, reload the
user, and expect the credits to be equal; reference DemoUserSeeder::run and the
User model for locating the relevant code and the seeder's idempotency logic.
app/Exceptions/Billing/InsufficientCreditsException.php (2)

24-24: 💤 Low value

Enhance the deprecation notice with removal timeline.

The @deprecated annotation would be more actionable if it included version information or a removal timeline (e.g., @deprecated since v0.1.0, will be removed in v0.2.0. Use forCost() instead.).

🤖 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/Exceptions/Billing/InsufficientCreditsException.php` at line 24, Update
the deprecation docblock on the InsufficientCreditsException deprecated element
to include version and removal timeline and preferred replacement (e.g.,
"`@deprecated` since v0.1.0, will be removed in v0.2.0. Use forCost() instead.");
locate the docblock in the InsufficientCreditsException class and replace the
existing "/** `@deprecated` Use forCost() */" text with a more informative
annotation that includes the "since" version, planned removal version, and the
suggested alternative method name for clarity.

9-9: ⚡ Quick win

Consider extending App\Exceptions\DomainException for consistency.

This exception extends \DomainException directly and manually implements DomainExceptionContract, while other domain exceptions in this PR (e.g., ConversionFailedException, FeatureNotAvailableException) extend the custom App\Exceptions\DomainException base class. Extending the base class would:

  • Eliminate duplicate contract implementation boilerplate
  • Ensure consistent exception hierarchy across the domain layer
  • Inherit the default details() implementation (though it's overridden here)
♻️ Proposed refactor
-use App\Exceptions\DomainExceptionContract;
+use App\Exceptions\DomainException;

-final class InsufficientCreditsException extends \DomainException implements DomainExceptionContract
+final class InsufficientCreditsException extends DomainException
🤖 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/Exceptions/Billing/InsufficientCreditsException.php` at line 9,
InsufficientCreditsException currently extends PHP's \DomainException and
implements DomainExceptionContract manually; change its declaration to extend
your custom App\Exceptions\DomainException (i.e., class
InsufficientCreditsException extends App\Exceptions\DomainException) and remove
the explicit "implements DomainExceptionContract" to match other domain
exceptions (ConversionFailedException, FeatureNotAvailableException), thereby
inheriting the contract and common behavior from the base class while keeping
any overridden details() method if needed.
app/Support/Conversions/Exceptions/UnsupportedConversionException.php (1)

10-10: ⚡ Quick win

Consider extending App\Exceptions\DomainException for consistency.

This exception extends \DomainException directly and manually implements DomainExceptionContract, while other domain exceptions in this PR extend the custom App\Exceptions\DomainException base class. Using the base class would ensure consistent exception hierarchy and eliminate contract implementation boilerplate.

♻️ Proposed refactor
-use App\Exceptions\DomainExceptionContract;
+use App\Exceptions\DomainException;
 use Throwable;

-final class UnsupportedConversionException extends \DomainException implements DomainExceptionContract
+final class UnsupportedConversionException extends DomainException
🤖 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/Support/Conversions/Exceptions/UnsupportedConversionException.php` at
line 10, UnsupportedConversionException currently extends PHP's built-in
\DomainException and implements DomainExceptionContract manually; change it to
extend your project's base App\Exceptions\DomainException (which already
implements DomainExceptionContract) and remove the redundant implements clause
so the class declaration becomes final class UnsupportedConversionException
extends App\Exceptions\DomainException, keeping the class name and final
modifier intact.
🤖 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 `@database/seeders/DemoConversionSeeder.php`:
- Around line 28-33: The FileRecord created in DemoConversionSeeder via
FileRecord::factory()->for($user)->create([...]) ($sourceFile) gets a storage
path but no physical file is written, which will cause downstream access to
fail; fix by locating DemoConversionSeeder and after creating $sourceFile write
a minimal stub file to the same storage path (use your Storage/filesystem API to
put a tiny placeholder content such as an empty PNG header or simple text) and
update the record’s size_bytes if necessary, or if you prefer keep it documented
instead, add a clear comment in DemoConversionSeeder noting that the seeded
FileRecord has no disk file and may break processing.
- Around line 35-42: The completed ConversionJob created by
ConversionJob::factory()->for($user)->for($sourceFile,
'sourceFile')->create(...) must include a valid result_file_id so downloads
work; create a demo result file (e.g., via File::factory()->for($user)->create()
or whatever file factory/creator exists), capture its id (e.g., $resultFile->id)
and pass 'result_file_id' => $resultFile->id into the
ConversionJob::factory()->create call (update the same block in
DemoConversionSeeder.php where the completed job is created).

In `@docs/release/mvp-scope-audit.md`:
- Line 77: The docs mention the wrong config filename; update the reference in
docs/release/mvp-scope-audit.md (the line containing
`config/conversion-cost.php`) to the correct filename
`config/conversion_costs.php`; also search for other occurrences of
`conversion-cost.php` in the docs and replace them with `conversion_costs.php`
so references (e.g., the one compared against in
docs/release/supported-conversions.md) are consistent.

In `@tests/Unit/Exceptions/DomainExceptionContractTest.php`:
- Line 8: The anonymous class in the test currently extends the global
DomainException (via "extends DomainException") instead of the project base
class; update the anonymous class declaration (the one created with "new
class('Test message.') extends DomainException implements
DomainExceptionContract") to extend the custom base class
App\Exceptions\DomainException (use the fully-qualified name or import it) so
the test validates the intended custom DomainException base class (matching the
second test which uses App\Exceptions\DomainException).

---

Nitpick comments:
In `@app/Exceptions/Billing/InsufficientCreditsException.php`:
- Line 24: Update the deprecation docblock on the InsufficientCreditsException
deprecated element to include version and removal timeline and preferred
replacement (e.g., "`@deprecated` since v0.1.0, will be removed in v0.2.0. Use
forCost() instead."); locate the docblock in the InsufficientCreditsException
class and replace the existing "/** `@deprecated` Use forCost() */" text with a
more informative annotation that includes the "since" version, planned removal
version, and the suggested alternative method name for clarity.
- Line 9: InsufficientCreditsException currently extends PHP's \DomainException
and implements DomainExceptionContract manually; change its declaration to
extend your custom App\Exceptions\DomainException (i.e., class
InsufficientCreditsException extends App\Exceptions\DomainException) and remove
the explicit "implements DomainExceptionContract" to match other domain
exceptions (ConversionFailedException, FeatureNotAvailableException), thereby
inheriting the contract and common behavior from the base class while keeping
any overridden details() method if needed.

In `@app/Support/Conversions/Exceptions/UnsupportedConversionException.php`:
- Line 10: UnsupportedConversionException currently extends PHP's built-in
\DomainException and implements DomainExceptionContract manually; change it to
extend your project's base App\Exceptions\DomainException (which already
implements DomainExceptionContract) and remove the redundant implements clause
so the class declaration becomes final class UnsupportedConversionException
extends App\Exceptions\DomainException, keeping the class name and final
modifier intact.

In `@database/seeders/DemoUserSeeder.php`:
- Around line 24-25: Remove the redundant foreign key from the relationship call
in DemoUserSeeder: instead of passing ['user_id' => $user->id] into
$user->creditAccount()->firstOrCreate(...), call firstOrCreate with an empty
search attributes array and the create attributes only (e.g., firstOrCreate([],
['balance' => 0'])) so the relationship applies the FK scope and you still set
balance when creating the record.

In `@docs/billing/stripe-webhooks.md`:
- Around line 22-24: Update the fenced code block that contains "POST
/stripe/webhook" to include a language specifier (e.g., add "http" or "txt"
after the opening backticks) so tooling and syntax highlighting work correctly;
locate the fenced block in the docs where the literal "POST /stripe/webhook"
appears and change the opening ``` to ```http (or ```txt) accordingly.

In `@docs/deployment/scheduler.md`:
- Around line 36-38: The fenced code block containing the cron entry should
include a language identifier for proper highlighting—update the block that
wraps the line "* * * * * cd /var/www/file-converter && php artisan schedule:run
>> /dev/null 2>&1" to use "cron" (i.e., replace the opening ``` with ```cron) so
the snippet is fenced as a cron code block.
- Around line 52-54: Update the fenced code block that contains the cron entry
"0 2 * * *  conversions:cleanup-expired" to include a language identifier (e.g.,
use ```text or ```plaintext) immediately after the opening backticks so the
block reads as a plain-text code block and renders correctly.

In `@docs/deployment/storage-and-permissions.md`:
- Around line 9-12: The fenced code block showing directory structure lacks a
language identifier; update the snippet in
docs/deployment/storage-and-permissions.md (the block containing
"storage/app/uploads/{user_id}/ — uploaded source files" and
"storage/app/conversions/results/{job_id}/ — conversion result files") to
include a language tag like ```text (or ```plaintext) at the opening fence so
the block becomes a highlighted plain-text code block.

In `@docs/development/local-development.md`:
- Around line 87-89: Update the fenced code block that currently contains the
sample branch name (the block with content "feature/CONV-XXX-kebab-title") to
include a language identifier by changing the opening fence from ``` to ```text
(or ```plaintext) so the block is rendered as plain text; locate the code block
in docs/development/local-development.md where the branch example appears and
modify the opening fence accordingly.
- Around line 93-95: Update the fenced code block that currently contains
"CONV-XXX: Short description in imperative mood" to include a language
identifier (e.g., use ```text or ```plaintext) so the block reads with a
specified language for proper rendering; locate the exact fenced block
containing the literal "CONV-XXX: Short description in imperative mood" and
prepend the language token to the opening backticks.

In `@README.md`:
- Around line 76-79: Update the fenced code block in README.md that shows the
demo credentials by adding a language identifier (e.g., change ``` to ```text or
```plaintext) so the block becomes fenced with a language for proper syntax
highlighting; locate the credential snippet containing "Email:   
demo@example.com" and "Password: password" and modify its opening fence
accordingly.
- Around line 131-135: Update the fenced code block containing the Stripe
environment variables to include a language identifier (e.g., use ```env or
```text) so the block becomes ```env followed by the
STRIPE_KEY/STRIPE_SECRET/STRIPE_WEBHOOK_SECRET lines and the closing ```,
ensuring README.md's code block is properly highlighted and identified.

In `@tests/Feature/Logging/ConversionLifecycleLoggingTest.php`:
- Around line 23-27: The test currently only asserts the presence of keys in the
log context for the 'conversion.job.created' expectation (the withArgs closure)
and the similar assertions at the two other occurrences; update each withArgs
closure in ConversionLifecycleLoggingTest to assert concrete expected values
instead of just isset — e.g. compare $context['conversion_job_id'] to the actual
ConversionJob id/UUID created in the test, $context['user_id'] to the test user
id, $context['converter_key'] to the expected converter key, and where
applicable assert specific error_code or status values; ensure you reference the
same message strings (like 'conversion.job.created') and the existing withArgs
closures when making these stronger assertions so the test will fail if
incorrect values are logged.

In `@tests/Feature/RateLimit/WebRateLimitTest.php`:
- Around line 20-27: Instead of manipulating the RateLimiter cache directly with
RateLimiter::hit($key) and RateLimiter::tooManyAttempts($key, 1), exercise the
named limiter through the HTTP layer: keep the RateLimiter::for('web-upload', fn
() => Limit::perMinute(1)->by($user->id)) setup, then perform two requests as
the test user against the route that uses the 'throttle:web-upload' middleware
(e.g. actingAs($user)->post('/your-upload-route') twice) and assert the second
response is a 429 (or contains the throttling response); this verifies the
middleware wiring and keying rather than only the cache counter.

In `@tests/Feature/Seeders/DemoUserSeederTest.php`:
- Around line 18-23: Add an assertion that the demo user's credit balance is
unchanged after re-running the seeder: before calling (new
DemoUserSeeder)->run() the second time, load the user
(User::where('email','demo@example.com')->first()) and store their credits
(field used by DemoUserSeeder for grants), run the seeder again, reload the
user, and expect the credits to be equal; reference DemoUserSeeder::run and the
User model for locating the relevant code and the seeder's idempotency logic.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 02201387-600b-406e-8386-5ff08600bd20

📥 Commits

Reviewing files that changed from the base of the PR and between c34caf5 and fe08ee7.

📒 Files selected for processing (58)
  • .env.example
  • README.md
  • app/Actions/Conversions/CreateConversionJobAction.php
  • app/Enums/FileFormat.php
  • app/Exceptions/Billing/InsufficientCreditsException.php
  • app/Exceptions/Conversions/ConversionFailedException.php
  • app/Exceptions/Conversions/ConversionResultExpiredException.php
  • app/Exceptions/DomainException.php
  • app/Exceptions/DomainExceptionContract.php
  • app/Exceptions/Features/FeatureNotAvailableException.php
  • app/Exceptions/Files/FileTooLargeException.php
  • app/Exceptions/Storage/StorageLimitExceededException.php
  • app/Http/Controllers/DownloadConversionResultController.php
  • app/Jobs/ProcessConversionJob.php
  • app/Providers/AppServiceProvider.php
  • app/Services/Billing/DatabaseCreditLedger.php
  • app/Support/Api/ApiExceptionMapper.php
  • app/Support/Conversions/Exceptions/UnsupportedConversionException.php
  • app/Support/Converters/Exceptions/InvalidConverterOptionsException.php
  • app/Support/Converters/Exceptions/UnsupportedFormatException.php
  • app/Support/Logging/BillingLogger.php
  • app/Support/Logging/ConversionLogger.php
  • app/Support/UI/UiDomainErrorMapper.php
  • app/Support/UI/UiErrorMessage.php
  • bootstrap/app.php
  • database/seeders/DatabaseSeeder.php
  • database/seeders/DemoConversionSeeder.php
  • database/seeders/DemoUserSeeder.php
  • docs/billing/stripe-webhooks.md
  • docs/deployment/production-checklist.md
  • docs/deployment/queue-worker.md
  • docs/deployment/scheduler.md
  • docs/deployment/storage-and-permissions.md
  • docs/development/local-development.md
  • docs/hardening/phase25-error-audit.md
  • docs/release/fresh-install-check.md
  • docs/release/mvp-scope-audit.md
  • docs/release/mvp-smoke-test-checklist.md
  • docs/release/quality-gate-result.md
  • docs/release/supported-conversions.md
  • tests/Feature/AccessibilitySmokeTest.php
  • tests/Feature/Api/V1/ApiErrorMappingTest.php
  • tests/Feature/Api/V1/ApiRateLimitTest.php
  • tests/Feature/Livewire/Billing/BillingPageTest.php
  • tests/Feature/Livewire/DashboardConverterTest.php
  • tests/Feature/Logging/BillingCreditLoggingTest.php
  • tests/Feature/Logging/ConversionLifecycleLoggingTest.php
  • tests/Feature/MVP/MvpHappyPathTest.php
  • tests/Feature/RateLimit/WebRateLimitTest.php
  • tests/Feature/Seeders/DemoConversionSeederTest.php
  • tests/Feature/Seeders/DemoUserSeederTest.php
  • tests/Unit/Exceptions/BillingAndFeatureExceptionsTest.php
  • tests/Unit/Exceptions/ConversionResultExceptionsTest.php
  • tests/Unit/Exceptions/ConverterExceptionsTest.php
  • tests/Unit/Exceptions/DomainExceptionContractTest.php
  • tests/Unit/Exceptions/OptionsAndFileExceptionsTest.php
  • tests/Unit/Support/Api/ApiExceptionMapperTest.php
  • tests/Unit/UI/UiDomainErrorMapperTest.php

Comment thread database/seeders/DemoConversionSeeder.php
Comment thread database/seeders/DemoConversionSeeder.php Outdated
Comment thread docs/release/mvp-scope-audit.md Outdated
Comment thread tests/Unit/Exceptions/DomainExceptionContractTest.php

@cubic-dev-ai cubic-dev-ai 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.

21 issues found across 58 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/Providers/AppServiceProvider.php">

<violation number="1" location="app/Providers/AppServiceProvider.php:44">
P2: Web rate limiters are registered but never applied, so upload/conversion requests remain effectively unthrottled.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/deployment/production-checklist.md Outdated
Comment thread docs/release/supported-conversions.md Outdated
Comment thread docs/release/mvp-smoke-test-checklist.md Outdated
Comment thread docs/release/mvp-smoke-test-checklist.md Outdated
Comment thread docs/release/mvp-smoke-test-checklist.md Outdated
Comment thread README.md Outdated
Comment thread docs/release/mvp-smoke-test-checklist.md Outdated
{
User::observe(UserObserver::class);

RateLimiter::for('web-upload', function (Request $request) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Web rate limiters are registered but never applied, so upload/conversion requests remain effectively unthrottled.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/Providers/AppServiceProvider.php, line 44:

<comment>Web rate limiters are registered but never applied, so upload/conversion requests remain effectively unthrottled.</comment>

<file context>
@@ -41,6 +41,18 @@ public function boot(): void
     {
         User::observe(UserObserver::class);
 
+        RateLimiter::for('web-upload', function (Request $request) {
+            return Limit::perMinute(20)->by(
+                $request->user()?->id ?: $request->ip()
</file context>

Comment thread app/Services/Billing/DatabaseCreditLedger.php Outdated
Comment thread .env.example Outdated
- ProcessConversionJob: wrap jobCompleted() in try/catch so a logging failure
  cannot flip a successful conversion to Failed
- DatabaseCreditLedger: guard all post-commit BillingLogger calls with report()
- bootstrap/app.php: API catch-all passes through HttpException so 404/405
  status codes are not collapsed to 500; import moved to top-level use
- DatabaseSeeder: guard demo seeders with environment check (local/dev/staging)
- DemoConversionSeeder: add result_file_id to completed job; add comment about
  no physical file on disk
- InsufficientCreditsException: extend App\Exceptions\DomainException (removes
  redundant implements clause)
- UnsupportedConversionException: extend App\Exceptions\DomainException
- DemoUserSeeder: remove redundant FK arg from firstOrCreate relationship call
- .env.example: remove CONVERTER_DEFAULT_RETENTION_DAYS (unused; retention is
  driven by config/feature-access.php)
- DomainExceptionContractTest: first test now extends project DomainException
  not global; both tests use the same base class
- ConversionLifecycleLoggingTest: assert concrete values (id, user_id,
  converter_key, error_code) not just key presence
- DemoUserSeederTest: idempotency test now asserts credit balance is unchanged
- docs: fix scheduler command (files:cleanup-expired, hourly not daily),
  PHP version (8.3 not 8.2), API docs URL (/docs/api not /api/docs),
  converter keys use colon format (png:jpg not png_to_jpg),
  JPG->PNG options corrected (resize/remove_metadata not quality),
  quality values corrected (medium/high/best, no low),
  smoke test API routes corrected, config filename corrected,
  phase25 audit summary table updated to reflect completed state

@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: 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 `@docs/deployment/scheduler.md`:
- Around line 26-27: Update the docs' manual test command to run the cleanup job
synchronously by default: replace or augment the example `php artisan
files:cleanup-expired` with `php artisan files:cleanup-expired --sync` (or add a
note that a queue worker must be running) so local verification of the
`files:cleanup-expired` command behaves deterministically without a background
worker.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 587fa454-82c4-4286-b578-cb0cd53420db

📥 Commits

Reviewing files that changed from the base of the PR and between fe08ee7 and cfd5aef.

📒 Files selected for processing (20)
  • .env.example
  • README.md
  • app/Exceptions/Billing/InsufficientCreditsException.php
  • app/Jobs/ProcessConversionJob.php
  • app/Services/Billing/DatabaseCreditLedger.php
  • app/Support/Conversions/Exceptions/UnsupportedConversionException.php
  • bootstrap/app.php
  • database/seeders/DatabaseSeeder.php
  • database/seeders/DemoConversionSeeder.php
  • database/seeders/DemoUserSeeder.php
  • docs/deployment/production-checklist.md
  • docs/deployment/scheduler.md
  • docs/hardening/phase25-error-audit.md
  • docs/release/fresh-install-check.md
  • docs/release/mvp-scope-audit.md
  • docs/release/mvp-smoke-test-checklist.md
  • docs/release/supported-conversions.md
  • tests/Feature/Logging/ConversionLifecycleLoggingTest.php
  • tests/Feature/Seeders/DemoUserSeederTest.php
  • tests/Unit/Exceptions/DomainExceptionContractTest.php
💤 Files with no reviewable changes (1)
  • .env.example
✅ Files skipped from review due to trivial changes (5)
  • docs/release/fresh-install-check.md
  • docs/release/mvp-smoke-test-checklist.md
  • docs/release/supported-conversions.md
  • docs/hardening/phase25-error-audit.md
  • docs/release/mvp-scope-audit.md
🚧 Files skipped from review as they are similar to previous changes (12)
  • database/seeders/DemoUserSeeder.php
  • docs/deployment/production-checklist.md
  • tests/Feature/Seeders/DemoUserSeederTest.php
  • README.md
  • database/seeders/DatabaseSeeder.php
  • tests/Unit/Exceptions/DomainExceptionContractTest.php
  • app/Jobs/ProcessConversionJob.php
  • bootstrap/app.php
  • tests/Feature/Logging/ConversionLifecycleLoggingTest.php
  • database/seeders/DemoConversionSeeder.php
  • app/Support/Conversions/Exceptions/UnsupportedConversionException.php
  • app/Services/Billing/DatabaseCreditLedger.php

Comment thread docs/deployment/scheduler.md Outdated

@cubic-dev-ai cubic-dev-ai 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.

5 issues found across 20 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/Providers/AppServiceProvider.php">

<violation number="1" location="app/Providers/AppServiceProvider.php:44">
P2: Web rate limiters are registered but never applied, so upload/conversion requests remain effectively unthrottled.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/release/supported-conversions.md Outdated
Comment thread docs/release/supported-conversions.md Outdated
Comment thread database/seeders/DemoConversionSeeder.php
Comment thread docs/release/supported-conversions.md Outdated
Comment thread bootstrap/app.php
…n JSON

- scheduler.md: add --sync flag to direct-run example
- supported-conversions.md: correct options for all 6 pairs (PNG->JPG had 4
  options not 1, JPG->PDF had 5 not zero, resize available on 4 pairs not 1)
- DemoConversionSeeder: write stub file to stored_path so download controller
  Storage::exists() check passes and demo result is servable
- bootstrap/app.php: unmapped HttpException on API routes now returns standardized
  JSON {error.code: http_error} with the exception's own status code instead of
  passing through to an HTML response; add Response import

@cubic-dev-ai cubic-dev-ai 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.

4 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/Providers/AppServiceProvider.php">

<violation number="1" location="app/Providers/AppServiceProvider.php:44">
P2: Web rate limiters are registered but never applied, so upload/conversion requests remain effectively unthrottled.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread docs/release/supported-conversions.md Outdated
Comment thread docs/release/supported-conversions.md Outdated
Comment thread docs/release/supported-conversions.md Outdated
Comment thread bootstrap/app.php Outdated
…arding

- supported-conversions.md: add remove_metadata to PNG->WEBP row and options
  availability list (PngToWebpConverter has 4 options not 3)
- supported-conversions.md: add fit_mode and compression to PNG->PDF row
  (PngToPdfConverter has same 5 options as JpgToPdfConverter)
- supported-conversions.md: replace fit_mode=stretch with fit_mode=original
  (stretch is not a valid value; actual allowed values are contain/cover/original)
- bootstrap/app.php: forward HttpException headers in the catch-all JSON response
  so status-specific headers (e.g. Allow on 405) are preserved
@menvil

menvil commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@menvil menvil merged commit 42bc741 into main Jun 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Triggers AI code review (CodeRabbit, Cubic)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant