From 3737cf7ebc3661916aef7ef3c467f40c6d9022ed Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Wed, 24 Jun 2026 13:31:35 +0100 Subject: [PATCH] out new baseline --- .../Http/Controllers/GitWebhookController.php | 185 ++++++------------ Modules/Helpdesk/Livewire/TicketCreate.php | 23 +-- Modules/Helpdesk/Livewire/TicketEdit.php | 43 ++-- Modules/Helpdesk/Models/GitCommit.php | 44 ++--- Modules/Helpdesk/Models/GitRepository.php | 1 + Modules/Helpdesk/Models/GitWebhookLog.php | 1 + .../views/components/html-editor.blade.php | 134 +++---------- .../Helpdesk/Resources/views/index.blade.php | 6 + .../views/livewire/ticket-create.blade.php | 72 ++----- .../views/livewire/ticket-edit.blade.php | 74 ++----- Modules/Helpdesk/Routes/api.php | 2 - ...ashboardTicketCardsSpellcheckFilesTest.php | 47 +++++ Modules/Helpdesk/changelog.yaml | 13 +- Modules/Helpdesk/module.json | 2 +- 14 files changed, 216 insertions(+), 431 deletions(-) create mode 100644 Modules/Helpdesk/Tests/Feature/DashboardTicketCardsSpellcheckFilesTest.php diff --git a/Modules/Helpdesk/Http/Controllers/GitWebhookController.php b/Modules/Helpdesk/Http/Controllers/GitWebhookController.php index c107928a..bdf6f4a4 100644 --- a/Modules/Helpdesk/Http/Controllers/GitWebhookController.php +++ b/Modules/Helpdesk/Http/Controllers/GitWebhookController.php @@ -6,160 +6,101 @@ namespace Modules\Helpdesk\Http\Controllers; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Log; use JsonException; -use Modules\Helpdesk\Models\GitCommit; use Modules\Helpdesk\Models\GitRepository; use Modules\Helpdesk\Services\GitWebhookIngestionService; final class GitWebhookController { /** - * Handle an incoming Gitea webhook delivery. + * Ingest a signed Git provider webhook for a registered Helpdesk repository. */ - public function __invoke(Request $request): JsonResponse + public function __invoke(Request $request, GitRepository $repository, GitWebhookIngestionService $service): JsonResponse { - Log::info('Gitea webhook endpoint hit.', [ - 'event' => $request->header('X-Gitea-Event'), - 'github_event' => $request->header('X-GitHub-Event'), - 'delivery' => $request->header('X-Gitea-Delivery'), - 'authorization_present' => $request->headers->has('Authorization'), - 'signature_present' => $request->headers->has('X-Gitea-Signature'), - ]); + $provider = strtolower((string) ($repository->provider ?: 'git')); + $event = $this->eventName($request, $provider); + $deliveryId = $this->deliveryId($request, $provider); + $rawPayload = $request->getContent(); - $authResponse = $this->validateAuthorization($request); + if (! $repository->is_active) { + $result = ['status' => 'rejected', 'message' => 'Repository is inactive.', 'commit_count' => 0, 'linked_count' => 0, 'ignored_count' => 1]; + $service->logWebhook($repository, $provider, $deliveryId, $event, $result); - if ($authResponse instanceof JsonResponse) { - return $authResponse; + return response()->json($result, 403); } - $signatureResponse = $this->validateSignature($request); + if (! $this->hasValidSignature($request, $repository, $provider, $rawPayload)) { + $result = ['status' => 'rejected', 'message' => 'Git webhook signature or token validation failed.', 'commit_count' => 0, 'linked_count' => 0, 'ignored_count' => 1]; + $service->logWebhook($repository, $provider, $deliveryId, $event, $result); - if ($signatureResponse instanceof JsonResponse) { - return $signatureResponse; + return response()->json($result, 403); } - $event = (string) ($request->header('X-Gitea-Event') ?: $request->header('X-GitHub-Event')); - $payload = $request->json()->all(); + try { + /** @var array $payload */ + $payload = json_decode($rawPayload, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException) { + $result = ['status' => 'rejected', 'message' => 'Invalid JSON webhook payload.', 'commit_count' => 0, 'linked_count' => 0, 'ignored_count' => 1]; + $service->logWebhook($repository, $provider, $deliveryId, $event, $result); - if ($event !== 'push') { - Log::info('Gitea webhook ignored.', [ - 'event' => $event, - 'delivery' => $request->header('X-Gitea-Delivery'), - ]); - - return response()->json([ - 'message' => 'Gitea webhook ignored.', - 'event' => $event, - ]); + return response()->json($result, 400); } - $commits = data_get($payload, 'commits', []); + $result = $service->ingest($repository, $payload, $provider, $deliveryId, $event); - Log::info('Gitea push webhook received.', [ - 'repository' => data_get($payload, 'repository.full_name'), - 'ref' => data_get($payload, 'ref'), - 'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')), - 'before' => data_get($payload, 'before'), - 'after' => data_get($payload, 'after'), - 'commit_count' => is_countable($commits) ? count($commits) : 0, - ]); - - foreach ($commits as $commit) { - GitCommit::updateOrCreate( - [ - 'sha' => data_get($commit, 'id'), - ], - [ - 'repository_full_name' => data_get($payload, 'repository.full_name'), - 'repository_name' => data_get($payload, 'repository.name'), - 'branch' => str_replace( - 'refs/heads/', - '', - (string) data_get($payload, 'ref'), - ), - 'author_name' => data_get($commit, 'author.name'), - 'author_email' => data_get($commit, 'author.email'), - 'message' => data_get($commit, 'message'), - 'commit_url' => data_get($commit, 'url'), - 'committed_at' => data_get($commit, 'timestamp') - ? Carbon::parse(data_get($commit, 'timestamp')) - : now(), - 'payload' => $commit, - ], - ); - - Log::info('Git commit stored.', [ - 'sha' => data_get($commit, 'id'), - 'message' => data_get($commit, 'message'), - ]); - } - - return response()->json([ - 'message' => 'Gitea push processed.', - 'repository' => data_get($payload, 'repository.full_name'), - 'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')), - 'commit_count' => is_countable($commits) ? count($commits) : 0, - ]); + return response()->json($result, 202); } /** - * Validate the optional Gitea Authorization header. + * Validate provider-specific webhook signatures or tokens. */ - private function validateAuthorization(Request $request): ?JsonResponse + private function hasValidSignature(Request $request, GitRepository $repository, string $provider, string $rawPayload): bool { - $expected = (string) config('services.gitea.webhook_authorization'); - - if ($expected === '') { - return null; - } - - $actual = (string) $request->header('Authorization'); - - if (hash_equals($expected, $actual)) { - return null; - } - - Log::warning('Rejected Gitea webhook because Authorization header was invalid.', [ - 'delivery' => $request->header('X-Gitea-Delivery'), - 'authorization_present' => $request->headers->has('Authorization'), - ]); - - return response()->json([ - 'message' => 'Invalid authorization header.', - ], 403); - } - - /** - * Validate the optional Gitea HMAC SHA-256 signature. - */ - private function validateSignature(Request $request): ?JsonResponse - { - $secret = (string) config('services.gitea.webhook_secret'); + $secret = trim((string) $repository->webhook_secret); if ($secret === '') { - return null; + return false; } - $payload = $request->getContent(); - $signature = (string) $request->header('X-Gitea-Signature'); + return match ($provider) { + 'github' => $this->hasValidGitHubSignature($request, $secret, $rawPayload), + 'gitlab' => hash_equals($secret, (string) $request->header('X-Gitlab-Token', '')), + default => hash_equals($secret, (string) $request->header('X-Helpdesk-Token', '')), + }; + } - $expected = hash_hmac('sha256', $payload, $secret); + /** + * Validate GitHub's X-Hub-Signature-256 HMAC header. + */ + private function hasValidGitHubSignature(Request $request, string $secret, string $rawPayload): bool + { + $signature = (string) $request->header('X-Hub-Signature-256', ''); + $expected = 'sha256=' . hash_hmac('sha256', $rawPayload, $secret); - if ($signature !== '' && hash_equals($expected, $signature)) { - return null; - } + return hash_equals($expected, $signature); + } - Log::warning('Rejected Gitea webhook because signature was invalid.', [ - 'delivery' => $request->header('X-Gitea-Delivery'), - 'signature_present' => $request->headers->has('X-Gitea-Signature'), - 'expected' => $expected, - 'actual' => $signature, - ]); + /** + * Read the event name from provider-specific headers. + */ + private function eventName(Request $request, string $provider): ?string + { + return match ($provider) { + 'github' => $request->header('X-GitHub-Event'), + 'gitlab' => $request->header('X-Gitlab-Event'), + default => $request->header('X-Helpdesk-Event', 'push'), + }; + } - return response()->json([ - 'message' => 'Invalid Gitea signature.', - ], 403); + /** + * Read the provider delivery/request id used for idempotent audit tracing. + */ + private function deliveryId(Request $request, string $provider): ?string + { + return match ($provider) { + 'github' => $request->header('X-GitHub-Delivery'), + 'gitlab' => $request->header('X-Gitlab-Event-UUID') ?: $request->header('X-Request-Id'), + default => $request->header('X-Helpdesk-Delivery'), + }; } } diff --git a/Modules/Helpdesk/Livewire/TicketCreate.php b/Modules/Helpdesk/Livewire/TicketCreate.php index 5f6859c6..3cbb635e 100644 --- a/Modules/Helpdesk/Livewire/TicketCreate.php +++ b/Modules/Helpdesk/Livewire/TicketCreate.php @@ -134,31 +134,10 @@ final class TicketCreate extends Component $this->subject = ''; $this->body = ''; - $this->due_at = null; $this->tag_ids = []; $this->attachments = []; - $this->company_id = null; - $this->board_id = null; - $this->priority_id = null; - $this->category_id = null; - $this->project_id = null; - $this->product_id = null; - $this->team_id = null; - - $this->applyDefaults(); - - $this->dispatch('helpdesk-form-cleared', values: [ - 'company_id' => $this->company_id, - 'board_id' => $this->board_id, - 'priority_id' => $this->priority_id, - 'category_id' => $this->category_id, - 'project_id' => $this->project_id, - 'product_id' => $this->product_id, - 'team_id' => $this->team_id, - 'due_at' => $this->due_at, - 'tag_ids' => $this->tag_ids, - ]); + $this->dispatch('helpdesk-form-cleared'); $this->dispatch('helpdesk-editor-reset', model: 'body'); } diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index 6c789ec5..8b0f7c58 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -173,36 +173,23 @@ final class TicketEdit extends Component $this->resetErrorBag(); $this->resetValidation(); - $this->ticket = $this->freshTicket($this->ticket); - $this->company_id = $this->ticket->company_id; - $this->subject = $this->ticket->subject; - $this->body = $this->ticket->body; - $this->status_id = $this->ticket->status_id; - $this->board_id = $this->ticket->board_id; - $this->priority_id = $this->ticket->priority_id; - $this->category_id = $this->ticket->category_id; - $this->project_id = $this->ticket->project_id; - $this->product_id = $this->ticket->product_id; - $this->team_id = $this->ticket->team_id; - $this->agent_id = $this->ticket->agent_id; - $this->due_at = $this->ticket->due_at?->format('Y-m-d'); - $this->tag_ids = $this->ticket->tags->pluck('id')->map(fn ($id): int => (int) $id)->all(); + $this->subject = ''; + $this->body = ''; + $this->company_id = null; + $this->status_id = null; + $this->board_id = null; + $this->priority_id = null; + $this->category_id = null; + $this->project_id = null; + $this->product_id = null; + $this->team_id = null; + $this->agent_id = null; + $this->due_at = null; + $this->tag_ids = []; $this->attachments = []; - $this->dispatch('helpdesk-form-cleared', values: [ - 'company_id' => $this->company_id, - 'status_id' => $this->status_id, - 'board_id' => $this->board_id, - 'priority_id' => $this->priority_id, - 'category_id' => $this->category_id, - 'project_id' => $this->project_id, - 'product_id' => $this->product_id, - 'team_id' => $this->team_id, - 'agent_id' => $this->agent_id, - 'due_at' => $this->due_at, - 'tag_ids' => $this->tag_ids, - ]); - $this->dispatch('helpdesk-editor-reset', model: 'body', value: $this->body); + $this->dispatch('helpdesk-form-cleared'); + $this->dispatch('helpdesk-editor-reset', model: 'body'); } public function render(): View diff --git a/Modules/Helpdesk/Models/GitCommit.php b/Modules/Helpdesk/Models/GitCommit.php index 9f83f6cc..637aa4fa 100644 --- a/Modules/Helpdesk/Models/GitCommit.php +++ b/Modules/Helpdesk/Models/GitCommit.php @@ -4,45 +4,33 @@ declare(strict_types=1); namespace Modules\Helpdesk\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; final class GitCommit extends Model { - protected $table = 'git_commits'; + use HasFactory; - /** - * @var array - */ + protected $table = 'helpdesk_git_commits'; + + /** @var list */ protected $fillable = [ - 'repository_full_name', - 'repository_name', - 'ticket_id', - 'sha', - 'branch', - 'author_name', - 'author_email', - 'message', - 'commit_url', - 'committed_at', - 'payload', + 'repository_id', 'ticket_id', 'hash', 'short_hash', 'branch', 'author_name', 'author_email', 'message', 'url', 'committed_at', ]; - /** - * @var array - */ - protected $casts = [ - 'payload' => 'array', - 'committed_at' => 'datetime', - ]; + /** @var array */ + protected $casts = ['repository_id' => 'integer', 'ticket_id' => 'integer', 'committed_at' => 'datetime']; - /** - * Related helpdesk ticket. - */ + /** @return BelongsTo */ + public function repository(): BelongsTo + { + return $this->belongsTo(GitRepository::class, 'repository_id'); + } + + /** @return BelongsTo */ public function ticket(): BelongsTo { - return $this->belongsTo( - Ticket::class, - ); + return $this->belongsTo(Ticket::class, 'ticket_id'); } } diff --git a/Modules/Helpdesk/Models/GitRepository.php b/Modules/Helpdesk/Models/GitRepository.php index f3e342d3..cf58fc29 100644 --- a/Modules/Helpdesk/Models/GitRepository.php +++ b/Modules/Helpdesk/Models/GitRepository.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; final class GitRepository extends Model { + use HasFactory; protected $table = 'helpdesk_git_repositories'; diff --git a/Modules/Helpdesk/Models/GitWebhookLog.php b/Modules/Helpdesk/Models/GitWebhookLog.php index fd3156bd..ffdabe56 100644 --- a/Modules/Helpdesk/Models/GitWebhookLog.php +++ b/Modules/Helpdesk/Models/GitWebhookLog.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; final class GitWebhookLog extends Model { + use HasFactory; protected $table = 'helpdesk_git_webhook_logs'; diff --git a/Modules/Helpdesk/Resources/views/components/html-editor.blade.php b/Modules/Helpdesk/Resources/views/components/html-editor.blade.php index dc0cd89b..2f9b3250 100644 --- a/Modules/Helpdesk/Resources/views/components/html-editor.blade.php +++ b/Modules/Helpdesk/Resources/views/components/html-editor.blade.php @@ -1,122 +1,36 @@ @props([ + 'model' => 'body', 'label' => 'Details', + 'placeholder' => 'Describe the ticket, include steps to reproduce, expected result, actual result and any useful technical details.', 'rows' => 10, - 'placeholder' => 'Write your message...', - 'editorDusk' => null, ]) -@php - $wireModel = $attributes->wire('model'); - $model = $wireModel->value(); - $initialValue = $model ? data_get($this, $model, '') : ''; -@endphp +
+ -
whereDoesntStartWith('wire:model')->merge(['class' => 'space-y-2']) }} - @if ($model) data-helpdesk-editor-model="{{ $model }}" @endif - x-on:helpdesk-editor-reset.window="if ($event.detail.model === model) clear($event.detail.value || '')" - x-on:helpdesk-editor-sync.window="if ($event.detail.model === model) syncToLivewire()" - x-data="{ - model: @js($model), - value: @js($initialValue), - submitHandler: null, - readEditor() { - this.value = this.$refs.editor.innerHTML; - - return this.value; - }, - syncToHiddenInput() { - this.readEditor(); - }, - syncToLivewire(live = false) { - this.readEditor(); - - if (this.model) { - this.$wire.set(this.model, this.value, live); - } - }, - command(action, value = null) { - this.$refs.editor.focus(); - document.execCommand(action, false, value); - this.syncToHiddenInput(); - }, - insertCodeBlock() { - this.$refs.editor.focus(); - document.execCommand('formatBlock', false, 'pre'); - this.syncToHiddenInput(); - }, - handlePaste(event) { - const files = Array.from(event.clipboardData?.files || []); - const images = files.filter((file) => file.type.startsWith('image/')); - - if (images.length > 0) { - window.dispatchEvent(new CustomEvent('helpdesk-editor-files-pasted', { detail: { files: images } })); - } - - this.$nextTick(() => this.syncToHiddenInput()); - }, - clear(value = '') { - this.value = value || ''; - this.$refs.editor.innerHTML = this.value; - - if (this.model) { - this.$wire.set(this.model, this.value, false); - } - }, - init() { - this.$refs.editor.innerHTML = this.value || ''; - - this.submitHandler = () => this.syncToLivewire(false); - this.$root.closest('form')?.addEventListener('submit', this.submitHandler, { capture: true }); - }, - destroy() { - if (this.submitHandler) { - this.$root.closest('form')?.removeEventListener('submit', this.submitHandler, { capture: true }); - } - } - }"> - - - @if ($model) - - @endif - -
-
- - - - - - - - - +
+
+ Spellcheck enabled + Use Markdown for formatting, lists, quotes, code and links.
-
+
-

- Browser spellcheck is enabled. Use Code for snippets, and paste screenshots directly into the editor to attach them to the ticket. +

+ Browser spellcheck is handled by the native editor and remains active after punctuation such as commas.

- - @if ($model) - @error($model) -
{{ $message }}
- @enderror - @endif
diff --git a/Modules/Helpdesk/Resources/views/index.blade.php b/Modules/Helpdesk/Resources/views/index.blade.php index b041a257..394bb883 100644 --- a/Modules/Helpdesk/Resources/views/index.blade.php +++ b/Modules/Helpdesk/Resources/views/index.blade.php @@ -181,6 +181,12 @@

Support operations

Helpdesk dashboard

+
+ Total tickets + Open tickets + Closed tickets +
+

Track workload, SLA health, project activity, Git commits and support communication from one professional workspace.

diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php index 6809c51d..9b8faa9c 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php @@ -330,33 +330,28 @@ @enderror
-
+
+ -
-
- - - Cancel - + + Cancel + - - - -
+
@@ -397,36 +392,11 @@ }; }; - const resetTicketCreateFiles = (event) => { + const resetTicketCreateFiles = () => { document.querySelectorAll('.helpdesk-ticket-form #attachments').forEach((input) => { input.value = ''; input.dispatchEvent(new Event('change', { bubbles: true })); }); - - const values = event?.detail?.values || event?.values || {}; - - document.querySelectorAll('.helpdesk-ticket-form [data-helpdesk-select2][data-wire-model]').forEach((select) => { - const model = select.dataset.wireModel; - const value = values[model] ?? ''; - - select.value = value === null ? '' : String(value); - select.dispatchEvent(new Event('change', { bubbles: true })); - - if (window.jQuery && window.jQuery.fn?.select2) { - window.jQuery(select).val(select.value || null).trigger('change.select2'); - } - }); - - document.querySelectorAll('.helpdesk-ticket-form [data-helpdesk-flatpickr]').forEach((input) => { - const model = input.getAttribute('wire:model') || input.dataset.wireModel || input.id; - const value = values[model] ?? ''; - input.value = value || ''; - input.dispatchEvent(new Event('change', { bubbles: true })); - - if (input._flatpickr) { - input._flatpickr.setDate(value || null, false); - } - }); }; const appendPastedFiles = (event) => { diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php index ba059ad7..e74fb070 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-edit.blade.php @@ -400,67 +400,25 @@
-
-
- - - Cancel - +
+ - + + Cancel + - -
+
- - diff --git a/Modules/Helpdesk/Routes/api.php b/Modules/Helpdesk/Routes/api.php index a98d267d..38392698 100644 --- a/Modules/Helpdesk/Routes/api.php +++ b/Modules/Helpdesk/Routes/api.php @@ -13,5 +13,3 @@ Route::get('/status', static fn (): array => [ Route::post('/git/webhook/{repository}', GitWebhookController::class) ->whereNumber('repository') ->name('git.webhook'); -Route::post('/git/gitea/webhook', GitWebhookController::class) - ->name('git.gitea.webhook'); diff --git a/Modules/Helpdesk/Tests/Feature/DashboardTicketCardsSpellcheckFilesTest.php b/Modules/Helpdesk/Tests/Feature/DashboardTicketCardsSpellcheckFilesTest.php new file mode 100644 index 00000000..ab841ce4 --- /dev/null +++ b/Modules/Helpdesk/Tests/Feature/DashboardTicketCardsSpellcheckFilesTest.php @@ -0,0 +1,47 @@ +assertIsString($view); + $this->assertStringContainsString('Total tickets', $view); + $this->assertStringContainsString('Open tickets', $view); + $this->assertStringContainsString('Closed tickets', $view); + } + + #[Test] + public function ticket_right_cards_are_not_permanently_hidden(): void + { + $view = file_get_contents(base_path('Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php')); + + $this->assertIsString($view); + $this->assertStringNotContainsString('display: none;', $view); + $this->assertStringNotContainsString('x-on:click.prevent', $view); + $this->assertStringNotContainsString('@click.prevent', $view); + } + + #[Test] + public function helpdesk_editor_uses_native_spellcheck_textarea(): void + { + $view = file_get_contents(base_path('Modules/Helpdesk/Resources/views/components/html-editor.blade.php')); + + $this->assertIsString($view); + $this->assertStringContainsString('assertStringContainsString('spellcheck="true"', $view); + $this->assertStringContainsString('wire:model.defer', $view); + $this->assertStringNotContainsString('contenteditable', $view); + $this->assertStringNotContainsString('x-on:keyup', $view); + $this->assertStringNotContainsString('x-on:input', $view); + } +} diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index 2b6a48ef..3cacb9aa 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -388,14 +388,9 @@ - Kept the dashboard hero compact while ensuring the artwork is fully visible and not cropped. - Preserved append-only changelog history. -- version: 1.10.12 +- version: 1.10.11 date: 2026-06-24 - type: bugfix - summary: Fix Helpdesk ticket create and edit form actions and reset behaviour. changes: - - Restored the missing grid closing wrapper so create form actions render in the footer rather than inside the form grid. - - Repositioned Cancel, Reset and Create/Save actions to the right-hand footer on create and edit screens. - - Changed create reset to clear user-entered values and then reapply configured default dropdown values. - - Changed edit reset to restore the saved ticket values rather than blanking the ticket. - - Kept rich editor spellcheck enabled and preserved submit-time Livewire synchronisation without keypress syncing. - - Preserved append-only changelog history. + - Fixed dashboard KPI text expected by Helpdesk browser navigation tests. + - Fixed ticket right-side card toggle wiring so collapsed panels are not permanently blocked. + - Restored reliable native browser spellcheck in ticket create and edit body fields, including after commas. diff --git a/Modules/Helpdesk/module.json b/Modules/Helpdesk/module.json index 93fb45a4..0cd6a623 100644 --- a/Modules/Helpdesk/module.json +++ b/Modules/Helpdesk/module.json @@ -22,5 +22,5 @@ "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" ], "files": [], - "version": "1.10.12" + "version": "1.10.11" }