Skip to content

release/v0.1.11: Phase 11 — Convert UI Flow#162

Merged
menvil merged 36 commits into
mainfrom
release/v0.1.11-phase11-convert-ui-flow
Jun 2, 2026
Merged

release/v0.1.11: Phase 11 — Convert UI Flow#162
menvil merged 36 commits into
mainfrom
release/v0.1.11-phase11-convert-ui-flow

Conversation

@menvil

@menvil menvil commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Phase 11 — Convert UI Flow (CONV-147–CONV-163)

Connects the dashboard converter UI to the backend job pipeline from Phase 9/10.

What's new

Convert flow:

  • convert() method in DashboardConverter — creates ConversionJob via CreateConversionJobAction, guards duplicate clicks
  • Steps: convert (pre-flight) → converting (spinner + polling) → completed / failed

UI states:

  • converting: spinner, source→target labels, wire:poll.2s on refreshConversionStatus
  • completed: Done message, filename, Download link, Change settings, Convert another
  • completed (expired): "This result has expired" message, no Download button
  • failed: user-friendly message, raw error hidden, Change settings / Try another file

Download route:

  • GET /conversions/{conversion}/download — owner-only, completed-only, expiry check, 403/404/410

Actions:

  • convertAnother() — full state reset to upload step
  • convertWithDifferentSettings() — keeps file/target/options, clears job, returns to settings

Factory states added:

  • ConversionJobFactory: queued(), processing(), completed(), failed()
  • FileRecordFactory: expired()

Model:

  • FileRecord::isExpired() helper

Test plan

  • composer test — 286/286 tests
  • composer lint — passes
  • npm run build — passes
  • Full happy-path integration test (CONV-163): Upload → format → settings → convert → completed → download
  • No billing/credits/history/API added

🤖 Generated with Claude Code


Summary by cubic

Connects the dashboard Convert UI to the backend pipeline so users can convert a file and download the result. Addresses CONV-147–CONV-163 with clear states, polling with timeout, better error handling, and a secure download route.

  • New Features
    • Dashboard UI triggers conversion via convert(), creates a ConversionJob, guards duplicate clicks, and shows friendly errors (unsupported conversion or generic failure).
    • Polling (wire:poll.2s on refreshConversionStatus) updates status to completed/failed and times out after 60 polls with a helpful message.
    • UI states: converting (spinner + source→target), completed (shows filename; expired hides Download), and failed (no raw error). Actions: convertAnother() and convertWithDifferentSettings() (clears stale job ID and returns to settings when possible).
    • Secure download: GET /conversions/{conversion}/download (owner-only, completed-only, result-present, expiry-aware; returns 403/404/410; preserves content type).
    • Factories/helpers: ConversionJobFactory (queued(), processing(), completed(), failed()), FileRecordFactory::expired(), and FileRecord::isExpired().

Written for commit 32e23ba. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Integrated file conversion workflow in the dashboard with real-time progress tracking
    • Added result file download capability with optional expiration support
    • Ability to retry conversions with different settings or convert another file
    • Enhanced UI with visual feedback for converting, completed, and failed states

menvil and others added 30 commits June 1, 2026 23:00
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on-test

CONV-147: Add convert button test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…button-to-create-conversion-job-action

CONV-148: Connect convert button to CreateConversionJobAction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nvert-protection

CONV-149: Add duplicate convert protection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tate-test

CONV-150: Add converting state test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ting-state-ui

CONV-151: Implement converting state UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…olling-test

CONV-152: Add job status polling test
refreshConversionStatus and wire:poll.2s implemented in CONV-151.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atus-polling

CONV-153: Implement job status polling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate-test

CONV-154: Add completed state test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ted-state-ui

CONV-155: Implement completed state UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-state-ui

CONV-157: Implement failed state UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oad-route-test

CONV-158: Add result download route test
Controller implemented in CONV-155. Fixed unused import lint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-download-route

CONV-159: Implement result download route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…her-action

CONV-160: Add convert another action
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-different-settings-action

CONV-161: Add convert with different settings action
menvil and others added 4 commits June 1, 2026 23:55
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed-ui-handling

CONV-162: Add result expired UI handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-integration-test

CONV-163: Add convert flow integration test
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a complete conversion result download feature, integrating job tracking into the Livewire dashboard, adding a gated download controller, and extending both the model layer and Blade template to support the full conversion lifecycle with authorization, expiration, and state management.

Changes

Conversion Result Download and Lifecycle Flow

Layer / File(s) Summary
File Expiration Infrastructure
app/Models/FileRecord.php, database/factories/FileRecordFactory.php
FileRecord::isExpired() checks if a stored expiration timestamp is past. Factory expired() state generates records with expires_at set one hour in the past for testing.
Download Controller and Route
app/Http/Controllers/DownloadConversionResultController.php, routes/web.php
New invokable controller validates conversion ownership (403), status (Completed only), result file presence, expiration (410), disk availability (404), then returns the file with correct Content-Type. Route registered as conversions.download.
Livewire Component Job Orchestration
app/Livewire/Dashboard/DashboardConverter.php
Tracks currentConversionJobId across steps. convert() creates a job and transitions to converting. getCurrentJobProperty() fetches the current job with relations. refreshConversionStatus() updates the step based on job status. convertAnother() and convertWithDifferentSettings() reset state for retries.
Conversion Lifecycle UI States
resources/views/livewire/dashboard/dashboard-converter.blade.php
Adds UI for failed (error messaging, retry actions), converting (polling progress, format labels), and completed (filename display, download link, change options). Updates convert step with full-width "Convert Now" button with loading state.
Test Factory States
database/factories/ConversionJobFactory.php
Provides queued(), processing(), completed(), failed() state methods for consistent factory-driven test setup across job lifecycle states.
Download Authorization and State Tests
tests/Feature/ConversionDownloadTest.php
Tests ownership validation (403), status filtering (404 for non-completed), expiration handling (410), and authentication requirement across the download endpoint.
End-to-End Conversion Flow
tests/Feature/ConversionFlowTest.php
Integration test covering upload → format selection → settings → conversion → status refresh → download verification as an authenticated user.
Livewire Component Lifecycle Tests
tests/Feature/Livewire/DashboardConverterConvertTest.php
Validates job creation, UI rendering for all states (failed/converting/completed), status refresh transitions, expiration behavior, flow actions (convertAnother/convertWithDifferentSettings), and duplicate prevention.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • menvil/FileConverter#90: Extends DashboardConverter to add conversion-job tracking and lifecycle methods that build on the earlier upload/step flow.
  • menvil/FileConverter#76: Introduces FileRecord with expires_at storage and expiration policy; this PR applies that infrastructure to gate downloads.
  • menvil/FileConverter#103: Adds target-format selection state/handlers to DashboardConverter; this PR adds job-driven conversion and status-refresh methods that extend that step progression.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The PR title clearly identifies the release version and phase focus (v0.1.11: Phase 11 — Convert UI Flow), accurately summarizing the main changeset which implements the conversion UI flow connecting dashboard converter to the backend conversion job pipeline.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.1.11-phase11-convert-ui-flow

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

❤️ Share

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

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

@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.

2 issues found across 10 files

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

Re-trigger cubic

Comment thread app/Livewire/Dashboard/DashboardConverter.php
Comment thread app/Livewire/Dashboard/DashboardConverter.php Outdated

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

🧹 Nitpick comments (3)
app/Http/Controllers/DownloadConversionResultController.php (1)

22-24: ⚡ Quick win

Reuse FileRecord::isExpired() instead of duplicating the expiry check.

This PR introduces FileRecord::isExpired(), which encodes the exact same logic. Using it here keeps the expiry rule in one place and avoids divergence later.

♻️ Proposed refactor
-        if ($file->expires_at !== null && $file->expires_at->isPast()) {
-            abort(410);
-        }
+        if ($file->isExpired()) {
+            abort(410);
+        }
🤖 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/Http/Controllers/DownloadConversionResultController.php` around lines 22
- 24, Replace the duplicated expiry check in DownloadConversionResultController
with the centralized method: instead of directly checking $file->expires_at and
isPast(), call FileRecord::isExpired() (or $file->isExpired() if it's an
instance method) to decide whether to abort(410); update the conditional to use
that method so the expiry logic is maintained in FileRecord::isExpired().
tests/Feature/ConversionDownloadTest.php (1)

10-101: 💤 Low value

Consider covering the missing-result-file and missing-on-disk 404 branches.

The controller has two additional 404 guards not exercised here: a completed job with result_file_id null (Line 18), and a result file whose stored_path is absent on the local disk (Line 26). Adding cases for these would lock in the full gating behavior.

🤖 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/ConversionDownloadTest.php` around lines 10 - 101, Add two
tests to cover the controller's 404 branches: (1) create a completed
ConversionJob (ConversionJob::factory()->for($user)->completed()->create()) with
result_file_id set to null and assert the GET route('conversions.download',
$job) returns 404; (2) create a FileRecord in the DB with 'stored_path' set
(FileRecord::factory()->for($user)->create([...])) but do NOT put the file into
Storage::disk('local') (use Storage::fake('local') without writing the file) and
attach its id to a completed ConversionJob so a download request to
route('conversions.download', $job) asserts 404; place these alongside the
existing tests to exercise the missing-result-file and missing-on-disk branches.
resources/views/livewire/dashboard/dashboard-converter.blade.php (1)

241-258: Polling has no terminal timeout for stuck jobs.

wire:poll.2s="refreshConversionStatus" only stops once the job reaches completed/failed. A job stuck in queued/processing (e.g., worker outage) will poll forever, keeping the user on this screen with no recovery path. Consider a max-wait threshold (e.g., transition to a "taking longer than expected" / failed state after N seconds) to bound the loop and improve resilience.

🤖 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 `@resources/views/livewire/dashboard/dashboard-converter.blade.php` around
lines 241 - 258, The converting view currently uses
wire:poll.2s="refreshConversionStatus" with no terminal timeout, so stuck jobs
poll forever; update the refreshConversionStatus Livewire action (and/or the
component state used by $step) to implement a max-wait threshold (e.g., track a
started_at timestamp or increment a retry counter on refresh) and transition
$step from 'converting' to a timeout state like 'taking_too_long' or 'failed'
after N seconds or N polls; ensure the Blade template ($step === 'converting')
and any UI for currentJob display handle the new state so the poll stops (remove
or stop polling when threshold reached) and the user sees recovery options.
🤖 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/Http/Controllers/DownloadConversionResultController.php`:
- Around line 14-18: The strict identity check in
DownloadConversionResultController::__invoke between $conversion->user_id and
$request->user()->id can fail due to type differences; fix it by normalizing
types: either add a cast for user_id in the ConversionJob model (add protected
$casts['user_id'] = 'integer' to app/Models/ConversionJob.php) or change the
authorization check in DownloadConversionResultController::__invoke to compare
ints (e.g., (int) $conversion->user_id === (int) $request->user()->id) so the
user_id comparison is type-safe.

In `@app/Livewire/Dashboard/DashboardConverter.php`:
- Around line 219-224: Wrap the call to CreateConversionJobAction::handle()
inside DashboardConverter::convert() in a try/catch similar to storeUpload():
catch UnsupportedConversionException and surface its message to the user (e.g.,
set a Livewire error/message and reset the component step/state to a safe
fallback), then catch any other Throwable to log the exception and show a
generic user-facing error and reset state; explicitly reference
CreateConversionJobAction::handle(), UnsupportedConversionException,
ConverterRegistry::find(), validateOptions(), ConversionJob::create(), and
ProcessConversionJob::dispatch() so you guard against failures from those calls
and ensure convert() fails gracefully rather than bubbling uncaught exceptions.

---

Nitpick comments:
In `@app/Http/Controllers/DownloadConversionResultController.php`:
- Around line 22-24: Replace the duplicated expiry check in
DownloadConversionResultController with the centralized method: instead of
directly checking $file->expires_at and isPast(), call FileRecord::isExpired()
(or $file->isExpired() if it's an instance method) to decide whether to
abort(410); update the conditional to use that method so the expiry logic is
maintained in FileRecord::isExpired().

In `@resources/views/livewire/dashboard/dashboard-converter.blade.php`:
- Around line 241-258: The converting view currently uses
wire:poll.2s="refreshConversionStatus" with no terminal timeout, so stuck jobs
poll forever; update the refreshConversionStatus Livewire action (and/or the
component state used by $step) to implement a max-wait threshold (e.g., track a
started_at timestamp or increment a retry counter on refresh) and transition
$step from 'converting' to a timeout state like 'taking_too_long' or 'failed'
after N seconds or N polls; ensure the Blade template ($step === 'converting')
and any UI for currentJob display handle the new state so the poll stops (remove
or stop polling when threshold reached) and the user sees recovery options.

In `@tests/Feature/ConversionDownloadTest.php`:
- Around line 10-101: Add two tests to cover the controller's 404 branches: (1)
create a completed ConversionJob
(ConversionJob::factory()->for($user)->completed()->create()) with
result_file_id set to null and assert the GET route('conversions.download',
$job) returns 404; (2) create a FileRecord in the DB with 'stored_path' set
(FileRecord::factory()->for($user)->create([...])) but do NOT put the file into
Storage::disk('local') (use Storage::fake('local') without writing the file) and
attach its id to a completed ConversionJob so a download request to
route('conversions.download', $job) asserts 404; place these alongside the
existing tests to exercise the missing-result-file and missing-on-disk branches.
🪄 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: 151de432-ca0e-44cc-9486-1d38a3c791de

📥 Commits

Reviewing files that changed from the base of the PR and between 3fcf671 and 90e1934.

📒 Files selected for processing (10)
  • app/Http/Controllers/DownloadConversionResultController.php
  • app/Livewire/Dashboard/DashboardConverter.php
  • app/Models/FileRecord.php
  • database/factories/ConversionJobFactory.php
  • database/factories/FileRecordFactory.php
  • resources/views/livewire/dashboard/dashboard-converter.blade.php
  • routes/web.php
  • tests/Feature/ConversionDownloadTest.php
  • tests/Feature/ConversionFlowTest.php
  • tests/Feature/Livewire/DashboardConverterConvertTest.php

Comment thread app/Http/Controllers/DownloadConversionResultController.php
Comment thread app/Livewire/Dashboard/DashboardConverter.php Outdated
menvil and others added 2 commits June 2, 2026 10:20
…ety, polling timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 11 review fixes: convert error handling, stale job ID, type safety, polling timeout
@menvil menvil merged commit 282ea85 into main Jun 2, 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