Skip to content

Add Post Settings to Custom Posts#25268

Merged
crazytonyli merged 15 commits intotrunkfrom
task/post-settings-refactor
Feb 24, 2026
Merged

Add Post Settings to Custom Posts#25268
crazytonyli merged 15 commits intotrunkfrom
task/post-settings-refactor

Conversation

@crazytonyli
Copy link
Contributor

Note

This PR will be merged after #25267.

Description

This PR introduces Post Settings and the Publish Sheet for custom posts, both under the same feature flag. There are a few features (adding terms, updating parent post) that are not implemented yet. But the UI is there, and the main refactor is complete and ready for review.

The existing Post Settings are reused, and the main changes are in the PostSettings and PostSettingsViewModel, which are updated to support AnyPostWithEditContext.

The commits are individually reviewable. But I can't say that's necessarily easier than reviewing the changes as a whole.

Testing instructions

The most important test is probably the regression post settings on Posts and Pages.

@crazytonyli crazytonyli added this to the 26.7 milestone Feb 19, 2026
@crazytonyli crazytonyli requested a review from kean February 19, 2026 07:20
@dangermattic
Copy link
Collaborator

dangermattic commented Feb 19, 2026

2 Warnings
⚠️ View files have been modified, but no screenshot or video is included in the pull request. Consider adding some for clarity.
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Feb 19, 2026

App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number31057
VersionPR #25268
Bundle IDorg.wordpress.alpha
Commit6c7db82
Installation URL2eg9en7u2k92o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Feb 19, 2026

App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number31057
VersionPR #25268
Bundle IDcom.jetpack.alpha
Commit6c7db82
Installation URL3ulo45fg5vjmg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Feb 19, 2026

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

Base automatically changed from task/cpt-control-more-row to trunk February 19, 2026 23:29
@Test("Post capabilities match expected values")
func testPostCapabilities() {
let caps = PostSettingsCapabilities.post()
#expect(caps.supportsCategories == true)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest removing these tests as they duplicate the implementation:

    /// Capabilities for the built-in "post" type.
    static func post() -> PostSettingsCapabilities {
        PostSettingsCapabilities(
            supportsCategories: true,
            supportsTags: true,
            supportsFeaturedImage: true,
            supportsExcerpt: true,
            supportsAuthor: true,
            supportsPostFormats: true,
            supportsComments: true,
            supportsTrackbacks: true,
            supportsPageAttributes: false,
            supportsSlug: true,
            supportsCustomFields: true,
            customTaxonomySlugs: []
        )
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleted in aa4d299.


// Categories and tags are not yet derived from taxonomies (see FIXME in implementation).
#expect(caps.supportsCategories == false)
#expect(caps.supportsTags == false)
Copy link
Contributor

Choose a reason for hiding this comment

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

(nit) simple boolean expectations can be written as :

#expect(caps.supportsTags)
#expect(!caps.supportsTags)

case .customPostTypes:
return BuildConfiguration.current == .debug
case .cptPostSettings:
return false
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't it be enabled for the .debug configuration like other CPT-related flags?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Post Settings feature was not quite ready when this PR was opened. But with this follow-up #25274, it's pretty close to complete. I have updated in 07358d1.

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't syncBlogAndAllMetadata now cover refreshing custom types #25267?

Copy link
Contributor

Choose a reason for hiding this comment

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

I also noticed new warnings in BlogService:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 8ae643b

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't syncBlogAndAllMetadata now cover refreshing custom types #25267?

This PR is a chained PR that was built on top of #25267. I have rebased, and those duplicated commits are now gone.

// Refresh post in to keep the post list up-to-date
do {
try await client.service.posts().refreshPost(
postId: updatedPost.id, endpointType: endpoint
Copy link
Contributor

Choose a reason for hiding this comment

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

(outside of the scope of PR) The fact that you need to manually refresh the cache seems like a bit of a design issue here.

static func == (lhs: Author, rhs: Author) -> Bool {
// The displayName may be fetched locally.
// Only id is sent to the API for updating author.
lhs.id == rhs.id
Copy link
Contributor

@kean kean Feb 20, 2026

Choose a reason for hiding this comment

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

I'd consider using Identifiable instead of Equatable for whatever scenario it is used for or excluding fields like displayName and avatarURL from PostSettings – it seems they belong elsewhere. It's additional information required for rendering the form. It's not required for post settings themselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The customized Equatable implementation is for the hasChanges = getSettingsToSave(for: new) != originalSettings statement. The originalSettings does not have displayName, because the name is not returned in the core REST API. The view model has code to update the Author instance with a displayName, so hasChanges would become true without this custom Equtable implementation, which is a bug because the author is not changed.

Considering the type Author here is internal to the Post Settings feature, I thought it'd be okay to change the Equtable implementation, instead of using the sythensized one.

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like an acceptable workaround, but I think I made a design mistake when I added displayName and avatarURL to PostSettings. These fields are not required to update the author – you only need an ID. How these authors are displayed in the UI are a separate concern.

I'm going to leave it up to you since it's not as straightforward to change the original design now.

otherTerms = [:]

// FIXME: Post metadata is not supported yet. Require wordpress-rs changes.
metadata = PostMetadata(from: .init())
Copy link
Contributor

@kean kean Feb 20, 2026

Choose a reason for hiding this comment

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

Yeah, metadata is not going to be easy.


/// Creates `PostUpdateParams` representing the diff between the post and
/// the current settings, for use with the WordPress REST API.
func makeUpdateParameters(from post: AnyPostWithEditContext) -> PostUpdateParams {
Copy link
Contributor

Choose a reason for hiding this comment

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

(just noting) This looks similar to how diffing works with WordPressKit and Posts. Unfortunately, we now have quite a lot of duplication, but there doesn't seem to be way around that.

}
featuredImageSection
if viewModel.isPost {
if viewModel.capabilities.supportsFeaturedImage {
Copy link
Contributor

Choose a reason for hiding this comment

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

(nit) It probably wouldn't nice to model features as an enum. The optimal way to represent these is a bitset (not necessarily recommending it, it doesn't matter in this case)`.

You'd have something like:

if viewModel.supports(.featuredImage)

return settings.slug
}
if let abstractPost {
return abstractPost.suggested_slug ?? ""
Copy link
Contributor

@kean kean Feb 20, 2026

Choose a reason for hiding this comment

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

The changes to PostSettingsViewModel are the only ones that look quite risky. It changes the existing code and introduces multiple if. I would suggest adding a protocol for PostSettingsViewModel and providing two separate implementation: one for Core Data (with no changes) and one for wordpress-rs. It should be easier to maintain and would reduce the risk of regressions. It's a classic refactoring strategy that should be helpful in other places when integrating wp-rs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I used an enum is to make code review easier. The code movement is clearer in the diff. But I should have changed the if-else statements to switch-case, which is done in 4f0477b.

I agree with you that using a protocol would make the code nicer, which I can do after the chained PR #25274 is merged. I hope that's okay with you?

Copy link
Contributor

Choose a reason for hiding this comment

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

That works, thanks! Ideally, I'd suggest using branch-by-abstraction as much as possible. It'll make it easier to review, reduce risk of regressions, and make our life easier once we are ready to remove the Core Data based types.

@kean
Copy link
Contributor

kean commented Feb 20, 2026

I'd also suggest running /review in CC locally. It has a few other suggestions in addition to what I found.

I smoke-tested the changes on regular posts.

@crazytonyli crazytonyli force-pushed the task/post-settings-refactor branch from c482a6f to 07358d1 Compare February 22, 2026 05:45
@crazytonyli crazytonyli requested a review from kean February 22, 2026 06:00
@crazytonyli
Copy link
Contributor Author

I have run the /review command. Here is the result and my notes (the command also produces some "good" comments, which I have removed). You and I may get different result from the some command. Let me know if you have any specific issues I should look into.


Architecture & Design

  • PostDetails as a private enum at file scope — Since it's used as a stored property in PostSettingsViewModel, making it a nested type inside the class (PostSettingsViewModel.PostDetails) would be more conventional and make the relationship clearer. Currently at PostSettingsViewModel.swift:908 it's declared at file scope.

I'll ignore this one.

  • CustomPostEditorService is a class with mutable state (PostSettingsViewModel.swift:337/CustomPostEditorService.swift:7) — post is mutated via update() and shared between the editor VC and the view model. This works but the shared mutable reference is something to be aware of. The onEditorPostSaved callback is set to a no-op (CustomPostEditorViewController.swift:448) with a comment saying the shared service is already up-to-date, which is correct but fragile — if someone later changes the update flow, the stale data could be subtle.

That's a good point, I probably could/should bind the CustomPostEditorService to the main actor. There will be further changes to CustomPostEditorService, so I'll keep this note in mind.

Specific Issues

  1. Commented-out code in PostSettingsCapabilities (PostSettingsCapabilities.swift:57-69) — Several capabilities are commented out with // details.taxonomies.... These are tracked by FIXME comments which is good, but the init(from:) initializer sets many features to false that the post type may actually support (categories, tags, page attributes, custom fields). Worth noting in the PR description which features are intentionally stubbed.

The comments are intentional, and some of them are addressed in #25274

  1. makeUpdateParameters has dead commented-out code (PostSettings.swift:317-322) — The parent page parameter block is commented out. Since there's already a TODO above it, this is acceptable, but the // prefix style mixed with // TODO: could be cleaner.

Again, these comments are intentional.

  1. tags: [] hardcoded in makeUpdateParameters (PostSettings.swift:342) — Tags are always sent as an empty array. This means if a post has tags, opening settings and saving will clear them. The FIXME at line 317 acknowledges this, but this is a potential data loss issue. Consider not including tags in the params at all when unchanged (pass nil instead of [] if the API supports it).

Again, it's intentional and is addressed in #25274

  1. Author fallback to "–" (PostSettings.swift:118) — When the author ID exists but the name isn't available, a dash character is shown. The resolveAuthorDisplayName() method in the VM (PostSettingsViewModel.swift:441) tries to resolve it from cached blog authors, but if that cache is empty, users see "–". This might be confusing in the UI.

Seeing "-" is not ideal. We can proactively fetch author again, instead of just quering blog.authors. I'll do some testing and address this in a separate PR if needed.

  1. PostFormat extensions duplicatedPostFormat.id (PostSettings.swift:391-403) and PostFormat.from(slug:) (PostSettingsViewModel.swift:879-891) are inverse mappings. Both have a TODO about exporting from wordpress-rs. These should ideally be colocated (both in PostSettings.swift or a shared extension file) rather than split across two files.

May be it's not obvious, but this is noted in the // TODO: Export from wordpress-rs comment. These two extensions will be removed once the proper changes are done in wordpress-rs.

  1. publishRemotePost merges settings + editor content (PostSettingsViewModel.swift:597-641) — The manual copying of PostUpdateParams properties is fragile as noted by the TODO. If a new field is added to PostUpdateParams, it could be missed here.

Again, it's covered by the code comment.

Testing

  • Missing: No tests for PostSettingsCapabilities.init(from:) or the PostSettingsViewModel remote post initializer path. The deleted PostSettingsCapabilitiesTests (from a previous commit) suggests these were intentionally removed — worth confirming that was deliberate.

Deleted.

Minor Nits

  • PostSettings.swift:141: trailing comment // after parentPageID = nil — looks like a leftover.

Indeed. Removed.

@kean
Copy link
Contributor

kean commented Feb 23, 2026

I have run the /review command. Here is the result and my notes (the command also produces some "good" comments, which I have removed).

I keep forgetting that LLMs generate different output each time 🤦 I'm pretty sure my review items were different.

@kean
Copy link
Contributor

kean commented Feb 23, 2026

Approved, left a couple of additional replies.

@sonarqubecloud
Copy link

@wpmobilebot wpmobilebot modified the milestones: 26.7, 26.8 Feb 23, 2026
@wpmobilebot
Copy link
Contributor

Version 26.7 has now entered code-freeze, so the milestone of this PR has been updated to 26.8.

@crazytonyli crazytonyli added this pull request to the merge queue Feb 23, 2026
Merged via the queue into trunk with commit da15e6d Feb 24, 2026
29 of 34 checks passed
@crazytonyli crazytonyli deleted the task/post-settings-refactor branch February 24, 2026 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants