workspaces()->pluck('id') as $workspaceId) { static::dispatch((string) $workspaceId); } } /** * Sync progress then broadcast to every workspace on the account. * Use when a step is account-scoped (e.g. MCP OAuth). */ public static function dispatchForAccount(Account $account, ?User $actor = null): void { if ($account->isOnboardingOpen()) { static::syncAfterCommit($account->id, $actor?->id); } } /** * Sync progress for the workspace account, then broadcast. * Actor-less flows (webhooks/jobs) fall back to the account owner. */ public static function dispatchForWorkspace(?string $workspaceId, ?User $actor = null): void { $account = filled($workspaceId) ? Workspace::query()->find($workspaceId)?->account : null; if ($account?->isOnboardingOpen()) { static::syncAfterCommit($account->id, $actor?->id); } } private static function syncAfterCommit(string $accountId, ?string $actorId): void { DB::afterCommit(function () use ($accountId, $actorId): void { $account = Account::query()->find($accountId); if ($account?->isOnboardingOpen()) { app(ResolveOnboardingStatus::class)->syncAndNotify($account, $actorId); } }); } public function broadcastAs(): string { return 'onboarding.status.updated'; } /** * @return array */ public function broadcastOn(): array { return [ new PrivateChannel("workspace.{$this->workspaceId}"), ]; } /** * @return array{workspace_id: string} */ public function broadcastWith(): array { return [ 'workspace_id' => $this->workspaceId, ]; } public function broadcastQueue(): string { return 'broadcasts'; } }