Skip to content

feat(repositories): choose between publish next and publish now - #15

Merged
sigmanor merged 3 commits into
mainfrom
feat/publish-now
Sep 2, 2026
Merged

sigmanor merged 3 commits into
mainfrom
feat/publish-now

Conversation

@sigmanor

@sigmanor sigmanor commented Sep 2, 2026

Copy link
Copy Markdown
Member

Why

The send button on a repository row promoted it to the head of the publication queue and nothing more, so a post that has to go out now still waited for the scheduled run. After a bad record was deleted from the queue once it had already been published, there was no way to get a replacement post out at all.

What

The button now opens a dialog with both choices:

The publication status is shown inside the dialog rather than as a toast, because that is the only place the per-integration outcome fits: one row per integration, a spinner while the request is in flight, then sent or failed with the error the connector reported, above a summary banner.

Deliberate choices

  • No faked progress. The backend is a single synchronous call, so rows go spinner → final result in one step. Flipping a row to sent on a timer would be a lie about a publication. (SSE would be honest but costs a second response mode, a hand-rolled stream parser, and dies behind any buffering proxy, to learn ~30 s earlier which connector is slow.)
  • The integration list costs no extra request — it comes from the api-configs the dashboard already loads. Content Maestro still resolves the enabled set itself, so a run can report an integration this tab's cached list does not know about (enabled in another tab) and can omit one the list still carries (disabled since). Rows are built from both, and an expected integration with no reported outcome is shown as a failure rather than silently as a success.
  • The send button is no longer disabled on the row that is already next. That is precisely the row most likely to need publishing now; only the Publish next action inside the dialog is disabled for it.
  • The 240 s abort is a safety net, not a cancellation. Aborting does not stop the run on the server, so the message says the publication may still be running and points at Cron History, instead of claiming it failed. A shorter timeout would read a legitimate 90 s Threads post as a failure. The dialog cannot be dismissed while a request is in flight, so its result is never thrown away.
  • A request that never produced a result leaves the choices in place with the error above them: the 409 refusals (a cron run holding the lock, an item already published) are worth another try, and the backend refuses a duplicate on its own.

Also fixes

promoteRepositoryToNext answers an unconfigured API with an error payload instead of throwing, and both handlers ignored it — so an unconfigured dashboard toasted "Repository will be published next" after doing nothing at all.

Tests

npx vitest run — 26/26. 12 new tests in src/utils/message-publish.test.ts cover the row-merging edge cases (no result yet, outcomes present, Go's null for empty slices, a failure with no message, a reported integration the dashboard did not expect, an expected one with no outcome) plus the summary tones.

npx tsc -b --force and npm run build are clean apart from two pre-existing errors in src/hooks/useRepositories.ts:396,411 that this branch does not touch.

npx eslint src could not be run: it is broken on main for unrelated reasons — Error while loading rule '@typescript-eslint/no-unused-expressions', failing on the untouched src/api/api-configs.ts.

Manual check worth doing

  1. Unposted row → send → Publish next → toast, row becomes head of the queue.
  2. Publish now with one integration disabled → only the enabled ones are listed.
  3. Publish now with a connector pointed at a dead URL → per-integration ✗ with its error, the row still leaves the queue, and Cron History shows a manual run with a working "Publish again".
  4. Content Maestro stopped → "Failed to connect to Content Maestro API".
  5. The same row in two tabs → the second gets "repository is already published".
  6. Mobile viewport for repository-mobile-view.

The send button promoted a repository to the head of the publication queue
and nothing more, so a post that has to go out now still waited for the
scheduled run. It now opens a dialog with both choices: promote to the head
of the queue, or publish immediately to every enabled integration through
Content Maestro's new /api/message/publish.

The publication status is shown inside the dialog rather than as a toast,
because that is the only place the per-integration outcome fits: one row per
integration, spinner while the request is in flight, then sent or failed with
the error the connector reported. Progress is not faked per integration - the
backend is a single synchronous call, and flipping a row to sent on a guess
would be a lie about a publication.

The integration list comes from the api-configs already loaded by the
dashboard, so the dialog costs no extra request. Content Maestro still
resolves the enabled set itself, so a run can report an integration this tab's
cached list does not know about; the rows are built from both, and an expected
integration with no reported outcome is shown as a failure rather than
silently as a success.

The send button is no longer disabled on the row that is already next: that is
precisely the row most likely to need publishing now. Only the "publish next"
action inside the dialog is disabled for it.

A request is given a 240 s abort as a safety net, not as a cancellation -
aborting does not stop the run on the server - so the timeout says the
publication may still be running instead of claiming it failed.

Also fixes an adjacent lie: promoteRepositoryToNext answers an unconfigured
API with an error payload instead of throwing, and the handlers ignored it, so
an unconfigured dashboard toasted "Repository will be published next" after
doing nothing at all.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 2, 2026

Copy link
Copy Markdown

Deploying content-sentinel with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5c3a96c
Status: ✅  Deploy successful!
Preview URL: https://943751c1.content-sentinel.pages.dev
Branch Preview URL: https://feat-publish-now.content-sentinel.pages.dev

View logs

Review of the previous commit found four ways the dialog could mislead or
trap the user.

One dialog instance serves every row, and nothing stopped a row's send button
from retargeting it while a publication was in flight. Repo A's request would
then land on a dialog now titled with repo B, showing A's outcome as B's. The
send button is now disabled while the dialog is working, the request is
aborted when the dialog is retargeted, and the response is dropped unless the
dialog still points at the repository it was made for.

The summary banner claimed the repository "left the queue" from the success
counts alone, so a run whose posted marking failed contradicted the
posted_error warning printed right below it - and sent the reader to "Publish
again" for a repost the scheduled run was about to do anyway. What it says
about the queue now follows `posted`.

Dismissal was blocked for any in-flight request, including the promotion,
which carries no timeout of its own: a stalled content-alchemist left a dialog
that could not be closed at all, where the confirm dialog it replaced could
always be cancelled. Only a publication blocks dismissal now, because only its
per-integration result exists nowhere else.

An empty integration list read as "no integration is enabled" even while the
configs were still loading, so opening the dialog during a cold load refused
to publish for no reason. The loading flag is threaded through and says so.

Also: the send button lost the "already next" wording when it stopped being
disabled for that row, leaving screen readers with no way to tell that row
apart - restored as part of the label. And promoting a repository the queue
has since moved past returned silently, closing the dialog as if it had
worked; it now says what happened.
Driving the dialog against a local Content Maestro showed every integration
row saying "Sending..." with a spinner before anything had been clicked: the
pending state is also the state the list is in before a publication starts.
The spinner now belongs to the publishing phase only, so the pre-run list
reads as what it is - where the post will go.
@sigmanor
sigmanor merged commit aa3beb7 into main Sep 2, 2026
1 check passed
@sigmanor

sigmanor commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

🎉 This PR is included in version 1.51.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant