- Fix UpdatePostRequest missing content_type, synced, meta fields
(content_type was silently dropped, causing Instagram Reels to post as Feed)
- Create API FormRequests (StorePostRequest, UpdatePostRequest) replacing inline validation
- Fix syntax errors in all publishers ($media->isVideo() missing variable)
- Fix Instagram Feed with single video calling publishSingleImage instead of publishReel
- Fix TikTok hardcoded SELF_ONLY privacy — now queries creator_info API
- Refactor YouTubePublisher to use google/apiclient SDK with chunked resumable upload
- Fix all publishers using file_get_contents for large videos (memory overflow)
— X, LinkedIn, LinkedInPage, Pinterest, Bluesky, Mastodon now use temp file + stream
- Fix Media::isVideo/isImage to use mime_type instead of extension
- Fix Threads not saving refresh_token (was null, now saves access_token)
- Add Instagram token refresh to publisher and ConnectionVerifier
- Fix PublishToSocialPlatform job: tries 3→1 (prevents duplicate uploads),
timeout 60→600s, added failed() method for cleanup
- Increase Horizon worker timeout 60→630s, Redis retry_after 90→660s
- Increase upload limit 500MB→1GB
- Add mastodon to getDefaultContentType in Edit.vue
- Refactor WorkspacePolicy to use pivot role instead of workspace.user_id
- Add manageBilling policy (owner only) to BillingController
- Fix ApiKeyController authorization (view → manageTeam for store/destroy)
- Fix WorkspaceInviteController using workspace.user_id for owner checks
- Fix WorkspaceController settings is_owner using workspace.user_id
- Create PostAction enum for UpdatePost/PostController action strings
- Create ApiToken\Status enum
- Add User::SUBSCRIPTION_NAME constant, replace all hardcoded 'default'
- Convert wantsEmailFor to accept NotificationType enum
- Convert all $data[] to data_get() across publishers, controllers, jobs
- Fix SocialLoginController callback missing try/catch
- Fix SocialController::toggleActive missing workspace null check
- Fix UpdatePost NPE on meta merge when postPlatform not found
- Remove HTML5 required attributes from form inputs
- Convert function declarations to arrow functions in Vue components
- Replace hardcoded URLs with Wayfinder route helpers
- Replace new Date() with dayjs
- Add 16 new test files covering policies, authorization, publishing
Backend:
- Create notifications table (user_id, workspace_id, type, channel,
title, body, data JSON, read_at, archived_at)
- Create Notification model with Type enum (post_failed,
account_disconnected, invite_received, member_joined, member_removed)
and Channel enum (email, in_app, both)
- Create SendNotification job: isolated from publish flow, handles
saving in-app notification and sending email independently
- NotificationController: index (excludes archived, scoped to workspace),
markAsRead, markAllAsRead, archiveAll
- Integrate with PublishToSocialPlatform (post failed/partial)
- Integrate with VerifyWorkspaceConnections (batch disconnection)
- Integrate with SocialAccount::markAsDisconnected (single disconnection)
- All use SendNotification::dispatch() instead of direct Mail::to()
Frontend:
- NotificationBell component in sidebar footer with unread badge
- Dialog with notification list, mark as read, mark all read, archive all
- Click navigates to relevant page (post edit, accounts)
- i18n for notifications UI (en, es, pt-BR)
Tests:
- 8 tests for NotificationController (auth, CRUD, workspace scoping)
- 4 tests for SendNotification job (channels, email, data storage)
All 745 tests passing.
Critical:
- Fix EnsureUserSetupIsComplete middleware route name prefixes and
redirect Subscription step to subscribe page (not onboarding)
- Fix MCP session pollution: Auth::setUser() instead of Auth::login()
- Remove dead BillingController::addWorkspace/removeWorkspace methods
- Remove broken Workspace::pendingInvites() method
Security (IDOR):
- MediaController: add workspace ownership verification on all endpoints
- UpdatePostRequest: scope label_ids validation to current workspace
- UpdatePostRequest: scope platform IDs validation to current post
Security (other):
- Fix open redirect in login and registration (validate internal URLs)
- Add validation to API PostController store/update (was $request->all())
- Prevent Owner role assignment via updateRole endpoint
- Fix API post author attribution to use workspace owner
Authorization:
- PostController: use createPost policy instead of view for store/update/destroy
Logic:
- Post Status enum labels now use translation system instead of hardcoded Portuguese
- Workspace deletion cleans up current_workspace_id for all affected members
- StoreWorkspaceInviteRequest: replace Portuguese validation messages with __()
Rename onboarding:
- Step1.vue -> Role.vue, Step2.vue -> Connect.vue
- Controller methods: step1->role, storeStep1->storeRole, step2->connect, storeStep2->storeConnect
All 728 tests passing.
- Add subscription check to AuthenticateApiToken and AuthenticateMcpToken
middlewares (returns 402 Payment Required if no active subscription)
- Skip subscription check in self-hosted mode
- Remove temporary OAuth debug log from SocialController
- Run composer dump-autoload to fix email_asset() helper autoloading
- Add 4 new tests for API subscription enforcement
All 728 tests passing.
Auth pages:
- Create AuthSplitLayout with animated feature slides (6 slides, 3 languages)
- All auth pages use split layout (form left, visual right)
- Add show/hide password toggle with tooltip on Register
- Legal footer only shown on Register via showLegal prop
Subscribe page:
- Redesign to match auth card pattern (centered, clean)
- Platform icons, feature checklist, dynamic trial days (trialDays - 1)
- Add "Switch workspace" link
- Full i18n (en, es, pt-BR)
Onboarding:
- Rename URLs: step1 -> role, step2 -> connect
- Add enforceStep() to prevent skipping/going back steps
- Redirect /onboarding to /onboarding/role
- Redesign Step2 with AuthSplitLayout and compact platform list
- 21 tests covering all step enforcement scenarios
Workspaces page:
- Redesign with AuthSplitLayout (list with avatars, current badge)
Language system:
- Move locale from DB to cookie (forever, unencrypted, session.domain)
- Create SetLocale middleware (sets cookie if missing, validates against config)
- Rename lang/pt-br to lang/pt-BR
- Add dayjs es locale
Other:
- Copy utils.ts from sendkit (formatNumber, formatMoney, copyToClipboard)
- ConfirmDeleteModal with text confirmation (sendkit pattern)
- i18n for ConfirmDeleteModal internal strings (common.php)
- EmptyState component for posts index
- Exact match for "All" posts in sidebar
- Posts breadcrumbs show current status filter
- DialogFooter buttons aligned left
- API Keys page redesign with Table, DropdownMenu, EmptyState
- Extract CreateApiKeyDialog and InviteMemberDialog to components
- Remove API Keys from sidebar
- DropdownMenuItem destructive variant for Remove action
Settings pages:
- Redesign layout to match Sendkit (max-w-4xl, space-y-12, Separator sections)
- Merge Members page into Workspace settings with Table, invite Dialog, ConfirmDeleteModal
- Add workspace logo upload/delete routes and controller methods
- Translate all hardcoded strings in Workspace.vue modals
Language system:
- Drop languages table, replace language_id FK with locale string column on users
- Create config/languages.php for available languages and default locale
- Add Spanish (es) translations (13 files)
- Simplify HandleInertiaRequests, ProfileController, RegisteredUserController
Code quality:
- Add declare(strict_types=1) to all PHP files
- Fix MastodonPublisher using wrong attribute (filename -> original_filename)
- Fix HasMediaTest for new has_photo/photo_url accessors
- Fix PublishToSocialPlatformTest type error revealed by strict_types
- Remove orphaned Language model from AppServiceProvider morph map
- Update User TypeScript interface (has_photo, photo_url, locale)
- Eager load media relation on workspaces to prevent N+1
- Add 8 new tests for workspace logo upload/delete
- Update workspace settings test to assert members/invitations props
All 710 tests passing.
- Add API rate limiting (60/min per workspace) in AppServiceProvider
- Add throttle:api middleware to all API routes
- Add rate limit exception rendering for JSON responses
- Add middleware priority list (AuthenticateApiToken before ThrottleRequests)
- Add ApiKeyControllerTest (6 tests for App dashboard CRUD)
- Remove duplicate test files (Controllers/ dir duplicates)
- Remove old controllers and routes/settings.php
- Add routes/mcp.php placeholder
- Add API Keys nav item to settings layout
- Fix LazilyRefreshDatabase conflict in API tests
- 698 tests passing
- Remove old root-level controllers (moved to App/ namespace)
- Remove old routes/settings.php (merged into routes/app.php)
- Add routes/mcp.php placeholder for future MCP server
- Add settings/ApiKeys.vue page with create/delete/copy token
- Add API Keys nav item to settings layout
- Add 26 API endpoint tests (auth, hashtags, labels, api-keys, workspace)
- Add ApiToken factory
- All 710 tests passing
- Extract business logic from controllers into Action classes:
Post/, Workspace/, Hashtag/, Label/, Invite/, ApiKey/
- Create subdomain routing: app.trypost.test (Inertia dashboard),
api.trypost.test (REST API with token auth)
- Add ApiToken model with tp_ prefix, token_lookup/hash auth
- Add AuthenticateApiToken middleware for API authentication
- Create Api controllers with JSON Resources for all entities
- Create App controllers that use Actions + Inertia responses
- Organize Form Requests into Api/ and App/ directories
- Add api_tokens migration
- Update all route names with app. prefix
- Update all tests to use new route names (684 passing)
Members invited to a workspace no longer need their own subscription
if the workspace owner has an active subscription or trial.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When clicking "Publish Now", the scheduled_at was keeping the original
scheduled time while published_at was set to the actual publish time.
This caused confusion in the UI showing different dates.
Now when status is 'publishing', scheduled_at is updated to now() before
dispatching the PublishPost job.
- Add many-to-many relationship between posts and workspace labels
- Add label selector in post edit page with autosave
- Display labels in posts list
- Add hashtags modal with search for appending hashtags to post content
- Fix sidebar active state for posts edit page
- Fix AvatarImage null src warning in SocialAccountsGrid
- Fix new post state by using Inertia::location for full page reload
- Hide close button on CommandDialog by default
- Move billing routes from /billing to /settings/billing
- Add billing translations (en/pt-br)
- Update billing/Index.vue to use $t() for all strings
- Fix all controllers using route('dashboard') to use route('calendar') or route('accounts')
- Replace ->with('error', ...) pattern with session()->flash('flash.banner', ...)
- Add self-hosted mode support for hasActiveSubscription() and workspace methods
- Add flash translations for account connection errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ConnectionVerifier now refreshes expired/expiring tokens before verification
- Prevents false disconnection emails when tokens are expired but refreshable
- Added refresh methods for LinkedIn, X, Bluesky, YouTube, TikTok, Pinterest, Threads
- Fixed dark mode for calendar post items (status colors)
- Fixed dark mode for platform tabs in post editor
- Added tests for token refresh behavior