url() which throws because * the local disk has no web-accessible URL. We store files on the local disk * intentionally — access is gated through WireChatAttachmentController which * verifies the requester is a conversation participant before streaming the file. */ class WireChatAttachment extends Attachment { protected function generateUrl(?string $path): ?string { if (! $path) { return null; } // Return the authenticated secure route instead of a direct storage URL. // The controller enforces participant-only access. $attachment = static::where('file_path', $path)->first(); if (! $attachment) { return null; } return route('wirechat.attachment.serve', ['attachment' => $attachment->id]); } }