feat: admin settings page UI/UX pass with WordPress-native patterns

- Header: h1 + logo in a wrapping flex row, hr.wp-header-end before the tabs,
  aria-current on the active tab; own CSS/JS enqueued only on this screen.
- Verbindung: ordered "So verbindest du Claude" steps, copy button (core
  clipboard.js + wp.a11y.speak), Site-Health-style status list, German details.
- Zugriff: roles as form-table fieldset, connections with site date format,
  empty-state row, confirm on revoke; ConnectionUsage records last use via
  WP_Application_Passwords::record_application_password_usage (once a day).
- Tools: plain-language legend, counter as inline notice, groups as core
  postboxes (postbox.js toggles, per-user closed state, summary when closed),
  fixed column widths with new "Art" column, submit above and below.
- Protokoll: site-timezone times with UTC tooltip, result icons, filters with
  labels in the list table nav, retention link to Limits.
- Limits: short labels with units and descriptions; snapshot count hidden but
  preserved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 16:50:58 +02:00
co-authored by Claude Opus 5.5
parent 5965e32acb
commit 843f45aeec
22 changed files with 1535 additions and 242 deletions
+15 -1
View File
@@ -130,10 +130,11 @@ final class LogTabTest extends TestCase
echo '<input type="hidden" name="_wpnonce" value="nonce-' . $action . '">';
});
Functions\when('wp_dropdown_users')->alias(static function (array $args): void {
echo '<select name="' . $args['name'] . '"><option>Alle</option></select>';
echo '<select name="' . $args['name'] . '" id="' . $args['id'] . '"><option>Alle</option></select>';
});
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://kunde.de/wp-admin/' . $path);
Functions\when('get_option')->justReturn([]);
Functions\when('add_query_arg')->alias(static fn (array $args, string $url): string => $url . '?' . http_build_query($args));
$log = Mockery::mock(ChangeLog::class);
$log->allows('abilities')->andReturn(['lit/content-update', 'lit/x"><script>']);
@@ -151,5 +152,18 @@ final class LogTabTest extends TestCase
self::assertStringContainsString('lit_mcp_clear_log', $html);
self::assertStringContainsString('confirm(', $html);
self::assertStringContainsString('nonce-lit_mcp_clear_log', $html);
// One intro line; the retention days link to the Limits tab.
self::assertMatchesRegularExpression('#<p class="description">[^<]*<a href="[^"]*tab=limits">90 Tagen</a>#', $html);
// Every filter control has a (screen-reader) label.
foreach (['ability', 'user_id', 'result', 'date_from', 'date_to'] as $name) {
self::assertMatchesRegularExpression(
'#<label class="screen-reader-text" for="lit-mcp-filter-' . str_replace('_', '-', $name) . '">#',
$html,
'label for ' . $name
);
}
self::assertStringContainsString('<option value="ok">erfolgreich</option>', $html);
self::assertStringContainsString('<option value="partial">teilweise</option>', $html);
}
}