Give each channel its own version of the post text - #9
Open
pallaoro wants to merge 2 commits into
Open
Conversation
pnpm 11 writes this key as a stub ("set this to true or false") on first
install and then refuses to install until someone decides, so `pnpm build`
failed on a fresh clone before reaching vite. esbuild and workerd need their
install scripts to produce a usable binary; sharp is vite's optional image
dependency.
The equivalent block in package.json ("pnpm": { "onlyBuiltDependencies" })
is dead — pnpm 11 reads this file instead.
One body of text sent verbatim to every platform is the compromise a multi-channel scheduler exists to avoid, and the composer made it worse: the character counter took the minimum limit across every selected channel, so adding X to a LinkedIn post silently capped that LinkedIn post at 280 characters. There was no way to say "short hook on X, the long version on LinkedIn" short of writing two posts. post_channels gains a `content` column. NULL means "inherit the shared draft" — so a post nobody customised stores exactly one body of text, and existing posts keep behaving identically. A blank override normalises to NULL rather than being stored as "", so emptying the box means "go back to the shared draft", never "publish nothing". publishToChannel now receives the resolved text from one helper, channelContent(), rather than posts.content directly, so publishing, the previews and the API can't disagree about which text wins. Two consequences worth naming: - The shared draft is now only measured against the channels still inheriting it. Give X its own version and LinkedIn stops being capped at 280. - Customising every channel and clearing the shared draft is a real post, not an empty one, so publishPost's precondition moves from "posts.content is non-empty" to "some channel has text to send". A channel that ends up with no text at all fails on its own and the others still go out. The editor and preview share one tab strip on purpose: two strips would let you type X's version while looking at LinkedIn's preview. PreviewChannelTabs is renamed ChannelTabs to match what it now does, and grows an "All channels" tab plus a dot marking the channels that carry their own version. Text length stays unenforced server-side, deliberately: bluesky.ts already states the house rule that the platform's own rejection is the source of truth. X counts weighted characters and Bluesky counts graphemes, so a JS .length check would reject valid posts and pass invalid ones. The per-platform counters remain composer guidance, as they were. Verified end to end against a local D1 and the running app: overrides publish to the right channel, a legacy request with no channel_content is unchanged, blank normalises to NULL, an all-customised post with no shared draft publishes, a genuinely empty one still 400s, and the save/reopen round-trip restores the overrides.
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.
Stacked on #8 — base it on
multi-image-publishingso the diff shows only this work. Retarget tomainonce #8 lands.The problem
One body of text sent verbatim to every platform is the compromise a multi-channel scheduler exists to avoid. The composer made it worse: the character counter took
Math.minacross every selected channel, so adding X to a LinkedIn post silently capped that LinkedIn post at 280 characters. There was no way to say "short hook on X, long version on LinkedIn" short of writing two posts.The shape
post_channelsgains acontentcolumn.NULLmeans "inherit the shared draft", so a post nobody customised still stores exactly one body of text and existing posts behave identically. A blank override normalises toNULLrather than""— emptying the box means "back to the shared draft", never "publish nothing".publishToChannelnow takes its text from one helper,channelContent(), instead of readingposts.contentdirectly, so publishing, previews and the API can't disagree about which text wins.Two consequences worth naming:
publishPost's precondition moves from "posts.contentis non-empty" to "some channel has text to send". A channel left with no text fails on its own and the others still go out.The editor and the preview share one tab strip on purpose — two strips would let you type X's version while looking at LinkedIn's preview.
PreviewChannelTabsis renamedChannelTabsto match what it now does, and grows an "All channels" tab plus a dot marking channels that carry their own version.What this deliberately does not do
Text length stays unenforced server-side.
bluesky.ts:169already states the house rule: the platform's own rejection is the source of truth, don't pre-guess limits. X counts weighted characters and Bluesky counts graphemes, so a JS.lengthcheck would reject valid posts and pass invalid ones. The per-platform counters remain composer guidance, as before. (Media limits stay enforced — an image count has no counting ambiguity.)Verification
Ran against a local D1 and the running app, not just typechecked:
channel_contentNULL400Also confirmed in the browser: the counter flips 257/280 → 257/3000 the moment X gets its own version, the inherited state renders read-only, and the preview follows the tab.
Notes for review
ALTER. Production is covered — the deploy runsapplySchemaWithReconcile, which adds the column additively. ButCREATE TABLE IF NOT EXISTSis a no-op against an existing local D1, so anyone with a pre-existing.wranglerstate needsALTER TABLE post_channels ADD COLUMN content TEXT;once. This is the template model's known local-dev drift, not new here.PUT /api/posts/:iddeletes and reinsertspost_channelswheneverchannel_idsis present, which resets each channel's delivery state (status/ref/url/error/attempts). Editing a published post therefore loses its delivery history. Out of scope for this PR; worth its own.pnpm-workspace.yamlcarried pnpm 11's unansweredallowBuildsstub, sopnpm buildfailed on a fresh clone before reaching vite.