fix(posts): block scheduling when content exceeds platform char limit - #26
Merged
paulocastellano merged 4 commits intoMay 11, 2026
Merged
Conversation
… limit Threads posts over 500 chars were saved + scheduled successfully and only failed inside the publish job. The frontend already showed the 537|500 badge but `canSchedule` ignored content length, so Schedule and Post Now stayed enabled. Backend `UpdatePostRequest` only capped at 63206 (Facebook's max), not per-platform. - Add `Platform::contentOverflow()` as the single source of truth and reuse it from `HasSocialHttpClient::validateContentLength` (publish-time). - New `ContentFitsPlatformLimits` rule applied to the `content` field on `App\\UpdatePostRequest`, `Api\\UpdatePostRequest`, and `Api\\StorePostRequest` via `Rule::when(...)` so drafts are not blocked. - Rule dedupes per platform (two Threads accounts -> one error) and reports the platform label, hard cap, and overage via i18n. - Edit.vue feeds `contentLengthOverflows` into `canSchedule` and lists each offending platform in `postActionTooltip` using the existing `getPlatformLabel` resolver.
…flow - Platform::contentOverflow returns int (0 = fits) via max(), drops the nullable-int + ternary pattern; HasSocialHttpClient and the validation rule updated to compare against 0 instead of null. - Drop the hardcoded 63206-char limit to 10000 across all entry points (Platform enum, MCP CreatePostTool/UpdatePostTool, FacebookRules, all three FormRequests). Facebook's API accepts up to 63206 but nobody writes 63k-char posts and emoji-heavy content risks overflowing the TEXT column's 65535-byte ceiling. - Compiled i18n JSON regenerated with the content_exceeds_platform key.
…t PostEditorHeader
Scheduled posts were silently editable: auto-save fired on every keystroke
with status='scheduled', and the backend either silently failed (per-platform
content-length, our new rule) or accepted invalid changes that blew up at
publish time. Editing a queued post is also a race with the publisher job.
UX change: scheduled state now reads-only the editor and exposes one
explicit escape — Unschedule to edit. The post drops to draft, user edits,
re-schedules. Matches Buffer/Hootsuite/Later/Postiz.
Implementation:
- New isLocked computed (= isReadOnly || isScheduled) gates save(),
triggerAutosave(), debouncedSave(), togglePlatform(). submit() stays on
isReadOnly so unschedulePost('draft') can still transition out.
- Editor body wraps in pointer-events-none + opacity-60 when isScheduled,
visually dimming inputs without per-input :disabled plumbing.
- Header swaps to a violet "This post is scheduled / Unschedule to edit"
banner replacing the entire normal action bar. Delete is hidden in
this state — only escape is Unschedule, then act on the draft.
Refactor: extracted the entire header into PostEditorHeader.vue (matches
the PostEditorComposer / PostEditorSidebar family). Single <header> root
with v-if/v-else branches by status. ~85 lines out of Edit.vue.
Cleanup: dropped orphan posts.edit.scheduled_for translation key from
en/pt-BR/es — replaced by the new scheduled_overlay_* keys.
paulocastellano
added a commit
that referenced
this pull request
May 12, 2026
Left over from the PostEditorHeader extraction in #26 — the header now derives its own isPublished from the post prop, and Edit.vue itself doesn't use it anywhere. ESLint caught it on CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Content exceeds Threads limit of 500 characters (537 provided). The editor was already showing the537|500badge — butcanScheduleignored content length, so Schedule and Post Now stayed enabled. BackendUpdatePostRequestonly cappedcontentat 63206 (Facebook's max), not per-platform.App\Rules\ContentFitsPlatformLimitsis applied to thecontentfield in all three FormRequests (App\Post\UpdatePostRequest,Api\Post\UpdatePostRequest,Api\Post\StorePostRequest) viaRule::when(...), so drafts pass and schedules/publishes are rejected when any selected platform's hard cap is exceeded.Edit.vuefeedscontentLengthOverflowsintocanSchedule, disabling the action buttons and surfacing each offending platform inpostActionTooltipvia the existinggetPlatformLabelresolver.Platform::contentOverflow()as the single source of truth — reused fromHasSocialHttpClient::validateContentLength(publish-time) so the schedule and publish paths cannot drift apart.Test plan
vendor/bin/pint --dirty --format agent— passedphp artisan test --compact --parallel— 1504 passed, 2 skipped, 16199 assertionsnpm run build— cleanContentFitsPlatformLimitsTest) + 4 feature inUpdatePostRequestTest(App path) + 4 feature inPostApiTest(API store/update + draft-bypass)Out of scope
platformContext's recommended length and doesn't know about other platforms attached to the same post. If you cross-post AI-generated content to a platform with a stricter cap, the new validation will block at the schedule step rather than letting the user discover it on publish.Platform::label()returns verbose names (Facebook Page, YouTube Shorts, Instagram (Standalone)). Frontend uses the cleanergetPlatformLabelmap. Unifying is a wider refactor.withValidatoris left as-is (pre-existing pattern, separate concern).