Skip to content

feat(website-content): require SEO fields to publish, and align the published control bar - #4009

Merged
ahtesham-quraish merged 4 commits into
mainfrom
ahtesham/published-bar-branch
Sep 29, 2026
Merged

ahtesham-quraish merged 4 commits into
mainfrom
ahtesham/published-bar-branch

Conversation

@ahtesham-quraish

@ahtesham-quraish ahtesham-quraish commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

Part of https://github.com/mitodl/hq/issues/13253

Description (What does it do?)

Two changes to the content editor's control bar and settings drawer.

1. The published bar is laid out like the edit bar.

It is the same bar in the same place, but the two looked unrelated: the published view ran the full width of the window, with Draft, Edit and the settings icon hard against the left edge and Status: Published against the right, while edit mode puts the status at the left end and the actions at the right, both lined up with the breadcrumb and the article text.

The published view now reuses the same ActionRow, so it gets the article's 890px centred column and the same ordering. Reusing the row rather than restyling it means the two cannot drift apart again — that row already carries a comment saying its width has to keep following the banner and the body, and now one row does that for both modes.

This also drops the flex wrapper that sat around the read-only toolbar. It set gap and margin-right on a position: fixed child, which it could not lay out either way, and now that the button group uses that same container inside the row, leaving it wrapped around the outside was actively misleading.

2. Publishing requires an SEO title and description.

They are what a search result and a link preview show. Without them the page head falls back to the content's title and whatever the body happens to open with — which for an unreadable body is the bare "Learn with MIT", the thin description the SEO rules document singles out. So publishing insists on both: the press opens the settings drawer instead, and resumes once they are written.

This is the same rule as topics, implemented the same way, and it has to hang on the publish rather than the save — a draft writes itself every couple of seconds and autosave cannot stop to ask. A draft may therefore sit without them; publishing is where they are insisted on.

Unlike topics it applies to news as well as articles: news has no topics section at all, so the SEO fields are the only thing its publish can ever be waiting on. That case is also what forced the resume to test !topicsRequired rather than a bare topic count — a length check alone would strand a news publish forever.

The drawer refuses to save while anything required is blank — on a draft as much as on something public. It marked both SEO fields required, put an asterisk on each label and said they were needed to publish, then let Save Settings through with both empty; topics behaved the same way. That collapses topicsMayNotBeEmptied and seoMayNotBeEmptied into topicsRequired and seoRequired: the two were always going to agree, and a second prop restating the first is what let the button and the label disagree. contentIsPublished stays, since it still picks which sentence a section shows.

A draft's content is untouched by this — autosave keeps writing it, and only the drawer's own settings wait on being complete. Cancel always works.

The fields are marked required, so each label carries an asterisk. That changed their accessible names, hence the query updates across three test files.

Screenshots (if appropriate):

The bar change is the alignment and ordering: the row now starts with the status and ends with the actions, within the article's column rather than the window's edges, matching the edit bar. Worth a look with an article open in each mode.

Pressing Publish on content with no SEO title or description now opens the settings drawer, with the section reading "Add an SEO title and description to publish your article."

How can this be tested?

  1. Open a published article. The bar reads Status: Published on the left, then Draft, Edit and the settings icon grouped on the right, all within the width of the article text below — not the window. Press Edit: same shape.
  2. On a draft with no SEO fields, press Publish. The settings drawer opens instead of the confirmation, and says why. Save Settings is disabled until both fields have content.
  3. Fill both in and save. The publish resumes, confirmation and all, and the write carries the SEO values.
  4. Reopen the drawer on that draft without pressing Publish. Save Settings is still disabled while either field is blank, and the asterisks say why. Cancel closes it; the body's autosave is unaffected.
  5. Type in the body of a draft with blank SEO and wait. It still autosaves; the requirement is the publish's alone.
  6. Repeat 2–4 on a news item, which has no topics section.
  7. On a published article, empty the SEO title in the drawer. The save is refused and the section says a published article needs both.
yarn test ArticleEditor NewsEditor ArticleSettingsDrawer WebsiteContentEditPage

Additional Context

Worth knowing before merge: any content with blank SEO fields — published or draft — cannot have its settings saved, or be published, until someone writes them. That is deliberate and what was asked for, but it is a content-operations consequence rather than a purely additive change: if there is a backlog of published articles with blank SEO, the next person to touch any of their settings has to fill them in first. Editing and autosaving the body is unaffected.

Tests: two layout cases for the published bar mirroring the edit-mode ones, and seven for the SEO rule — publish held back, one field not being enough, whitespace not passing for a title, the resume, the drawer's refusal while a publish waits, a published item not being allowed to empty them, and a draft being allowed to sit without them. Plus the news-only case, which is the one that would catch the stranded-publish bug.

Also removed a duplicated assertion in a published article offers Draft, Edit and Settings — the same Unpublish Article check appeared twice, left behind by an earlier merge.

Unrelated pre-existing flake, not introduced here: ArticleEditor.happydom.test.tsx fails intermittently in a full-file run, most often on an article that has never been saved is created once, then updated, with an act(...) warning or a timed-out autosave wait. I verified it fails on this branch's parent with these changes stashed (2 of 3 runs), and that the test passes in isolation. The cause is vendor/.../toolbar/toolbar.tsx:38 — useToolbarNavigation observes its whole subtree with a MutationObserver and calls setItems(collectItems()) unconditionally from the callback, which happy-dom delivers as a microtask outside React's act. The fix is probably to skip that setItems when the collected items have not changed, which also stops a re-render per DOM mutation in production. Worth its own PR.

🤖 Generated with Claude Code

It is the same bar in the same place, but the two looked unrelated: the
published view ran the full width of the window with its buttons against
the left edge and the status against the right, while edit mode puts the
status at the left end and the actions at the right, both lined up with
the breadcrumb and the text.

The published view now reuses the same `ActionRow`, so it carries the
article's 890px column and cannot drift from edit mode when that column
changes.

Also drops the flex wrapper around the read-only toolbar. It set gap and
margin on a `position: fixed` child, which it could not lay out either
way, and now that the button group uses the same container inside the
row its presence there was actively misleading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ahtesham-quraish
ahtesham-quraish requested a review from a team as a code owner September 29, 2026 08:57
Copilot AI balanced review requested due to automatic review settings September 29, 2026 08:57
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Nested toolbar padding and the action-group margin prevent the advertised edge alignment.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Aligns published website-content controls with the centered edit-mode action row.

Changes:

  • Reuses ActionRow for published controls.
  • Adds layout regression tests and removes a duplicate assertion.
  • Two gutter/alignment issues remain.
File Description
WebsiteContentEditor.tsx Restructures the published toolbar.
ArticleEditor.happydom.test.tsx Tests published toolbar structure and ordering.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

They are what a search result and a link preview show, and without them
the page head falls back to the title and whatever the body happens to
open with -- which is the thin description the SEO rules single out. So
publishing now insists on both: the press opens the settings drawer
instead, and resumes once they are written.

Held to the same rule as topics, and implemented the same way, for the
same reason -- it has to be the publish and not the save, because a
draft writes itself every couple of seconds and autosave cannot stop to
ask. Unlike topics this applies to news as well: news has no topics
section, so the SEO fields are the only thing its publish can wait on.

The drawer refuses to save while a publish is waiting on it and still
has not got what it needs. Saving closes the drawer and closing forgets
the press, so allowing it would drop the publish with nothing on screen
to say why -- a hole the topics rule had too.

`awaitingTopicsForPublish` becomes `awaitingSettingsForPublish`, since it
now covers either requirement, and the resume tests what the drawer just
handed over rather than state that has not landed. `!topicsRequired` in
that test is what keeps news from stranding: its drawer sends no `topics`
at all, so a bare length check would never resume.

Messages key on whether the content is published rather than on whether
the save is refused. The two used to coincide and no longer do -- a
draft's save is refused too while a publish waits -- and telling a draft
it is published would simply be wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ahtesham-quraish ahtesham-quraish changed the title fix(website-content): lay the published control bar out like the edit one feat(website-content): require SEO fields to publish, and align the published control bar Sep 29, 2026

@daniellefrappier18 daniellefrappier18 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking but worth a mention - the "Required" indicator on SEO fields misrepresents actual enforcement.

  1. You open a brand-new, empty draft article and click Settings.
  2. The SEO Title and SEO Description fields show a red asterisk (*) next to their labels, and a screen reader announces them as "required."
  3. You leave both fields blank and click "Save Settings." It saves fine — nothing stops you.
Screen.Recording.2026-09-29.at.12.39.27.PM.mov

Ahtesham Quraish and others added 2 commits September 29, 2026 22:04
The drawer marked both SEO fields required, put an asterisk on each
label, and said an SEO title and description were needed to publish --
then let Save Settings through with both empty. Same for topics.

The refusal now follows the requirement, on a draft as much as on
something public. That collapses `topicsMayNotBeEmptied` and
`seoMayNotBeEmptied` into `topicsRequired` and `seoRequired`: the two
were always going to agree, and a second prop that only ever restated
the first was the reason the button and the label could disagree in the
first place. `contentIsPublished` stays, since it still picks which
sentence a section shows.

A draft's *content* is untouched by this -- autosave keeps writing it,
and only the drawer's own settings wait on being complete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ahtesham-quraish
ahtesham-quraish merged commit f60be1a into main Sep 29, 2026
15 checks passed
@ahtesham-quraish
ahtesham-quraish deleted the ahtesham/published-bar-branch branch September 29, 2026 17:25
This was referenced Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants