Skip to content

fix(admin): add publish-date editing - #2201

Merged
khoinguyenpham04 merged 5 commits into
emdash-cms:mainfrom
masonjames:codex/edit-published-date
Aug 4, 2026
Merged

fix(admin): add publish-date editing#2201
khoinguyenpham04 merged 5 commits into
emdash-cms:mainfrom
masonjames:codex/edit-published-date

Conversation

@masonjames

@masonjames masonjames commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a publish-date field to the content settings panel for published entries. Editors and administrators can update the date, while authors retain the existing read-only permissions. The value is normalized to an ISO timestamp and sent through the existing content update endpoint.

Closes #2178

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/... (N/A: bug fix)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5)

Screenshots / test output

  • pnpm --filter @emdash-cms/admin exec vitest run tests/components/ContentSettingsPanel.test.tsx tests/router.test.tsx — 32 tests passed
  • pnpm --filter @emdash-cms/admin test — 1,240 tests passed
  • pnpm typecheck
  • pnpm lint
  • pnpm format

The coverage includes Arabic/RTL rendering, editor authorization, the exact update payload, and concurrent auxiliary updates while a publish-date request remains pending.

@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6839ab2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2201

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2201

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2201

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2201

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2201

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2201

emdash

npm i https://pkg.pr.new/emdash@2201

create-emdash

npm i https://pkg.pr.new/create-emdash@2201

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2201

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2201

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2201

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2201

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2201

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2201

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2201

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2201

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2201

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2201

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2201

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2201

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2201

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2201

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2201

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2201

commit: 6839ab2

@masonjames
masonjames marked this pull request as ready for review July 22, 2026 14:36
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jul 22, 2026

@emdashbot emdashbot Bot 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.

This is the right change, in the right place. The publish-date field plugs into the existing auxiliary-update path for content metadata, and the backend already has schema and authorization support (publishedAt on contentUpdateBody, gated by content:publish_any in the update route). The frontend correctly limits the control to editor+ users and already-published items, the strings are wrapped for Lingui, the new UI uses logical Tailwind classes, and the added tests cover role gating and the update payload.

I checked the full diff, the backend route (packages/core/src/astro/routes/api/content/[collection]/[id].ts), the content update handler/repository, the Zod schema, the role/permission mapping, and the admin tests. Only one real issue stood out: the isUpdatingPublishedAt flag is derived from the latest variables of a shared updateMutation, so it can flip to false while a publish-date update is still in flight. That clears the input/button disabled state too early and allows duplicate/conflicting submissions when other auxiliary writes (SEO, author, or even a save) happen concurrently.

Comment thread packages/admin/src/router.tsx Outdated
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review size/L overlap and removed size/M review/needs-review No maintainer or bot review yet labels Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 26, 2026

@emdashbot emdashbot Bot 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.

This is the right change in the right place, and the prior review’s finding is fully addressed. The publish-date field plugs into the existing content update path, the backend already supports publishedAt writes gated by content:publish_any, and the frontend correctly restricts the control to editor+ users on already-published entries.

What I checked:

  • Diff coverage: ContentSettingsPanel, ContentEditor, router.tsx, the admin API client types, the existing datetime-local helpers, the changeset, and the added tests.
  • Backend support: packages/core/src/astro/routes/api/content/[collection]/[id].ts requires content:publish_any for any publishedAt write; the contentUpdateBody schema accepts publishedAt; handleContentUpdate passes it through to ContentRepository.update. content:publish_any maps to the EDITOR role, which matches the frontend’s ROLE_EDITOR = 40 check.
  • Prior issue fixed: The previous problem was that isUpdatingPublishedAt was inferred from the latest variables of a shared updateMutation, so it could drop to false while a publish-date update was still in flight if another auxiliary write (SEO, author, etc.) started. The author introduced a dedicated publishedAtMutation, and isUpdatingPublishedAt now tracks publishedAtMutation.isPending only. The new router test holds the publish-date PUT open, fires an SEO update, and asserts the publish-date trigger stays disabled — a direct regression test for the prior bug.
  • Conventions: Strings are Lingui-wrapped (t\Publish date`, t`Update publish date``); the new layout uses logical/vertical Tailwind spacing; the changeset is concise and user-facing; no new SQL, no new logged-out query path, no justification/reviewer comments added.
  • Edge cases: The input is disabled during the update; the button is disabled when the value is empty, unchanged, or a request is in flight; storedPublishedDate is synced back into local state when the item or its publishedAt changes; empty/cleared values are not submitted.

There are no blocking issues or convention violations. The implementation is consistent with how scheduling and other auxiliary metadata writes are handled, and the regression test makes the fix durable.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 26, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since cla: signed and removed review/needs-rereview Author pushed changes since the last review labels Jul 26, 2026

@khoinguyenpham04 khoinguyenpham04 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for addressing this @masonjames. Just one small timezone issue. datetime-local uses local time, but these helpers treat it as UTC, so users outside UTC will save a shifted publish time.

Could we convert it like the scheduler does with new Date(publishedDate).toISOString()?

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Jul 27, 2026
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 28, 2026

@emdashbot emdashbot Bot 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.

This is the right change in the right place: the backend already accepts publishedAt on the content-update body and gates it behind content:publish_any (EDITOR+), and the frontend correctly restricts the control to published entries for editor+ users. The dedicated publishedAtMutation also fixes the prior concurrency issue where isUpdatingPublishedAt was inferred from a shared mutation's latest variables.

However, on a fresh read I caught a timezone-bug the prior review missed. ContentSettingsPanel displays publishedAt through toDatetimeLocalInputValue, which keeps the value in UTC (2025-01-15T10:30:00.000Z2025-01-15T10:30). But the submit handler converts with new Date(publishedDate).toISOString(), which parses the bare datetime-local value as local time. On any non-UTC client the persisted timestamp shifts by the local offset every edit, directly contradicting the datetime-local.ts helper documentation that was added specifically to avoid this mistake. This is a data-integrity regression for a field whose purpose is precise backdating.

The fix is small: use fromDatetimeLocalInputValue instead of new Date(...).toISOString(). The existing test needs to stop stubbing Date and stop asserting the buggy shifted value.

What I checked:

  • Diff coverage: ContentSettingsPanel, ContentEditor, router.tsx, API client type, changeset, and tests.
  • Backend authorization: packages/core/src/astro/routes/api/content/[collection]/[id].ts requires content:publish_any for publishedAt writes; content:publish_any maps to EDITOR (40), matching the frontend ROLE_EDITOR check.
  • Schema: contentUpdateBody accepts publishedAt as an ISO datetime/null.
  • Conventions: strings are Lingui-wrapped, layout uses logical Tailwind classes, no new SQL/logged-out query paths, changeset is concise and user-facing.
  • The router regression test for concurrent publish-date/SEO updates is a durable test of the prior bug.

Comment thread packages/admin/src/components/ContentSettingsPanel.tsx Outdated
Comment thread packages/admin/tests/components/ContentSettingsPanel.test.tsx Outdated
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 28, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Jul 28, 2026
@khoinguyenpham04

Copy link
Copy Markdown
Collaborator

Your earlier code was right, @masonjames, my suggestion introduced the timezone shift. Could we switch back to fromDatetimeLocalInputValue and update the test accordingly? Sorry for the confusion!

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Jul 28, 2026
@masonjames

Copy link
Copy Markdown
Contributor Author

@khoinguyenpham04 no worries!

@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 28, 2026

@emdashbot emdashbot Bot 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.

This is the right follow-up: the PR wires the admin publish-date control through the existing backend capability (publishedAt on contentUpdateBody, gated by content:publish_anyRole.EDITOR), uses a dedicated mutation so isUpdatingPublishedAt no longer collides with other auxiliary writes, and restricts the UI to published entries for editor+ users.

The previously flagged timezone regression is fixed:

  • ContentSettingsPanel now displays publishedAt via toDatetimeLocalInputValue and submits via fromDatetimeLocalInputValue, keeping the value as UTC (2025-01-15T10:30:00.000Z2025-01-15T10:302020-06-01T08:45:00.000Z) instead of re-parsing the datetime-local string as local time.
  • The component test no longer stubs Date and asserts the stable UTC output.
  • The router regression test verifies that publish-date updates fly independently of concurrent SEO updates and that the pending state only disables the publish-date control.

What I checked:

  • Authorization parity: frontend uses ROLE_EDITOR = 40, backend content:publish_any maps to Role.EDITOR (40), and the route rejects publishedAt writes without that permission.
  • Schema: contentUpdateBody.publishedAt accepts an ISO datetime/null with offset: true.
  • Conventions: the new strings (Publish date, Update publish date) are Lingui-wrapped, the new layout uses non-directional Tailwind utilities (space-y-2, pt-2), and the changeset is user-facing and concise.
  • No new SQL, no logged-out route queries, no new indexes, no stale comments introduced.
  • Tests cover RTL rendering, role gating, the exact update payload, and concurrent-update isolation.

Everything in the diff is consistent with EmDash’s patterns, and the data-integrity issue from the prior review is resolved. LGTM.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 28, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the codex/edit-published-date branch from 6e95d38 to 9c13c9c Compare July 28, 2026 17:59
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 4, 2026

@emdashbot emdashbot Bot 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.

This re-review confirms the approved follow-up state still holds. The PR wires the admin publish-date control through the existing backend capability (publishedAt on contentUpdateBody, gated server-side by content:publish_anyRole.EDITOR), uses a separate mutation so the UI can isolate the publish-date pending state, and restricts the field to entries that already have a publishedAt value for editor+ users.

What I re-checked:

  • Authorization parity — the route at packages/core/src/astro/routes/api/content/[collection]/[id].ts rejects publishedAt writes unless the actor has content:publish_any, which maps to Role.EDITOR (40). The frontend gating uses the same ROLE_EDITOR = 40 threshold.
  • Timezone/data integritytoDatetimeLocalInputValue slices the stored UTC ISO value to YYYY-MM-DDTHH:mm, and fromDatetimeLocalInputValue appends :00.000Z, so the persisted timestamp is UTC-stable rather than reinterpreted as local time.
  • SchemacontentUpdateBody.publishedAt accepts an ISO datetime/null with offset: true.
  • Conventions — new UI strings (Publish date, Update publish date) are Lingui-wrapped; layout uses non-directional Tailwind utilities (space-y-2, pt-2); the changeset is concise and user-facing; no new messages.po churn.
  • Cross-cutting concerns — no new SQL, no logged-out-route queries, no new indexes, no stale comments. Tests cover RTL rendering, role gating, the exact publishedAt payload, and concurrent-update isolation.

The diff is consistent with EmDash’s existing patterns and the previously-flagged timezone/data-integrity issue is resolved. No blocking or suggestion-level findings remain.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 4, 2026
@khoinguyenpham04
khoinguyenpham04 merged commit 2f1219e into emdash-cms:main Aug 4, 2026
47 checks passed
@emdashbot emdashbot Bot mentioned this pull request Aug 4, 2026
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.

Content publishing - Configurable publish date

2 participants