Hi Salvatore,
You’re right that this should be clearer — thanks for the feedback!
Troy Client blocks its own deactivation while plugins that depend on it are still installed. Troy Client delivers plugin updates through Troy — an independent update system I built last year. A number of my plugins already depend on it. The lock icon on the Plugins screen lists exactly which plugins are holding it in place — once those are removed, Troy Client unlocks and can be deleted.
That said, the “why” wasn’t easy to find. Troy is a new project, and I’m still working on making this stuff obvious. Two things changed since your review:
If you can replace extension functionality with a few lines in a mu-plugin (that’s advanced!), you’re probably not the target audience for the extensions — and that’s fine. Everything is open source. The core plugin is already a fully fledged SEO suite, and it sounds like that’s all you need.
I appreciate you taking the time to write this up. This is exactly the kind of feedback that helps me improve. Thank you!
As for the breadcrumbs: the inline style in the body is valid HTML5 and renders correctly. If you’d like to customize the breadcrumb styling, feel free to open a support thread and I’ll gladly help you sort it out.
Thank you for the detailed response — the dedicated page and the upcoming link in the plugin row are genuinely good improvements.
Two things I’d still push back on:
**Troy Client and removal.** The SEO Framework itself installs and updates perfectly fine through WordPress.org without Troy. Troy Client only enters the picture if you install the Extension Manager — which many users will do just to try the free extensions or to run the Yoast import. If they later decide the extensions aren’t for them and remove the Extension Manager, they’d reasonably expect the slate to be clean. Leaving a component behind that can only be removed via FTP isn’t a UX edge case — it’s a trust issue, especially for a plugin that markets itself on being lightweight and non-intrusive.
**The inline <style> in the body.** You mention it’s valid HTML5, and technically that’s true — the spec does permit <style> outside <head>. In practice though, it’s still considered poor form: it blocks rendering, it can interfere with CSP headers, and it places presentation logic where content is expected. Injecting it via wp_head would be the standard WordPress approach and would sidestep all of those concerns.
about code fix i used this:
<?php
/**
* Plugin Name: Fix SEO Framework
* Description: Removes inline CSS and indicators from The SEO Framework, adds breadcrumb styles on singular posts only, redirects attachments to their parent page, and hides the Extensions button in the admin.
* Author: Salvatore Noschese
*/
// Remove the "Generated by The SEO Framework" indicator from the HTML source
add_filter( 'the_seo_framework_indicator', '__return_false' );
// Remove inline <style> block and current-page breadcrumb item from the breadcrumb output
add_filter( 'the_seo_framework_breadcrumb_shortcode_output', function( $html, $atts ) {
$patterns = [
'/<style\b[^>]*>.*?<\/style>/is',
'/<li[^>]*>(?:(?!<li).)*aria-current[^<]*<\/span>\s*<\/li>/s'
];
return preg_replace( $patterns, '', $html );
}, 10, 2 );
// Redirect attachment pages to their parent post, or to the homepage if orphaned
add_action( 'template_redirect', function() {
if ( is_attachment() ) {
$parent_id = get_post()->post_parent;
$redirect = $parent_id ? get_permalink( $parent_id ) : home_url( '/' );
wp_redirect( $redirect, 301 );
exit;
}
} );
// Output breadcrumb styles in the <head> on singular posts/pages only
add_action( 'wp_head', function() {
if ( is_singular() ) {
echo <<<CSS
<style>
nav.tsf-breadcrumb ol {
display: inline;
list-style: none;
margin-inline-start: 0;
padding: 0;
}
nav.tsf-breadcrumb ol li {
display: inline;
}
nav.tsf-breadcrumb ol li a {
text-decoration: none;
}
nav.tsf-breadcrumb ol li:not(:last-child)::after {
content: '\\00BB';
margin-inline-end: 1ch;
margin-inline-start: 1ch;
}
</style>\n
CSS;
}
} );
best regards 😊