diff --git a/docs/superpowers/plans/2026-04-26-wordpress-content-sync-admin-hardening.md b/docs/superpowers/plans/2026-04-26-wordpress-content-sync-admin-hardening.md
new file mode 100644
index 0000000..4ed082c
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-26-wordpress-content-sync-admin-hardening.md
@@ -0,0 +1,849 @@
+# Admin Workflow and Hardening Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Complete the plugin's usable admin workflow for configuring sync pairs, testing connections, importing/exporting package files, viewing operation results/logs, and running a final smoke checklist with hardened security and user-facing errors.
+
+**Architecture:** Phase 6 keeps the current import-first sync engine and adds admin workflows around it. Settings stay option-backed, state-changing admin actions use dedicated controllers with capability and nonce checks, and templates receive pre-sanitized view data while still escaping every output at render time.
+
+**Tech Stack:** PHP 7.4, WordPress admin APIs, WordPress HTTP/REST APIs, PHPUnit, PHPStan, PHPCS/WPCS, existing `SettingsRepository`, `RestTransportClient`, `JsonFileTransport`, `SyncEngine`, and `OptionLogger`.
+
+---
+
+## File Structure
+
+- `src/Settings/Settings.php`: expand typed settings to include sync pair credentials, URL mappings, selected content types, default direction, and retention/debug controls.
+- `src/Admin/AdminNotice.php`: value object for redirect-driven admin notices with `type`, `message`, and optional result context.
+- `src/Admin/AdminNoticeRepository.php`: converts `$_GET` query flags into safe notices for templates.
+- `src/Admin/SettingsController.php`: handles settings saves through `admin_post_wpcs_save_settings`.
+- `src/Admin/ConnectionTestController.php`: handles connection diagnostics through `admin_post_wpcs_test_connection`.
+- `src/Admin/FileExportController.php`: exports a valid empty package scaffold for a configured pair until full extraction is implemented.
+- `src/Admin/LogController.php`: handles log clearing through `admin_post_wpcs_clear_logs`.
+- `src/Admin/AdminPage.php`: registers the new controllers and passes settings/notices/logs to the dashboard template.
+- `src/Logging/OptionLogger.php`: add safe read/clear helpers for operation history.
+- `src/Transport/RestTransportClient.php`: treat REST `accepted: false` JSON responses as remote rejections.
+- `templates/admin/dashboard.php`: replace the placeholder dashboard with forms for settings, diagnostics, file import/export, operation history, and smoke guidance.
+- `tests/Unit/Admin/*Test.php`: unit coverage for notices, settings save, connection diagnostics, export, log clearing, and dashboard rendering.
+- `tests/Unit/SettingsTest.php`: settings expansion and sanitization coverage.
+- `tests/Unit/Transport/RestTransportClientTest.php`: REST rejected-body coverage.
+- `docs/smoke/phase-6-admin-hardening.md`: manual smoke checklist and known local environment notes.
+
+---
+
+## Task 1: Expand Settings for Admin Workflow
+
+**Files:**
+- Modify: `src/Settings/Settings.php`
+- Modify: `tests/Unit/SettingsTest.php`
+
+- [ ] **Step 1: Write failing settings tests**
+
+Add tests to `tests/Unit/SettingsTest.php`:
+
+```php
+public function test_it_sanitizes_full_admin_workflow_settings(): void {
+ $settings = Settings::fromArray(
+ array(
+ 'sync_pairs' => array(
+ array(
+ 'name' => 'Production to Staging',
+ 'source_url' => 'https://example.test/',
+ 'destination_url' => 'https://staging.example.test/',
+ 'username' => '',
+ 'application_password' => 'secret app password',
+ 'default_direction' => 'push',
+ 'content_types' => array( 'posts', 'terms', 'media', 'bad_type' ),
+ 'url_mappings' => array(
+ array(
+ 'source' => 'https://example.test',
+ 'destination' => 'https://staging.example.test',
+ ),
+ ),
+ ),
+ ),
+ 'log_retention' => '50',
+ 'debug_logging' => '1',
+ )
+ );
+
+ $pairs = $settings->syncPairs();
+
+ self::assertSame( 'Production to Staging', $pairs[0]['name'] );
+ self::assertSame( 'https://example.test/', $pairs[0]['source_url'] );
+ self::assertSame( 'https://staging.example.test/', $pairs[0]['destination_url'] );
+ self::assertSame( 'codex', $pairs[0]['username'] );
+ self::assertSame( 'secret app password', $pairs[0]['application_password'] );
+ self::assertSame( 'push', $pairs[0]['default_direction'] );
+ self::assertSame( array( 'posts', 'terms', 'media' ), $pairs[0]['content_types'] );
+ self::assertSame(
+ array(
+ array(
+ 'source' => 'https://example.test',
+ 'destination' => 'https://staging.example.test',
+ ),
+ ),
+ $pairs[0]['url_mappings']
+ );
+ self::assertSame( 50, $settings->logRetention() );
+ self::assertTrue( $settings->debugLoggingEnabled() );
+}
+```
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter SettingsTest`
+
+Expected: FAIL because `logRetention()`, `debugLoggingEnabled()`, and expanded sync pair keys do not exist.
+
+- [ ] **Step 3: Implement settings expansion**
+
+Update `Settings` to support:
+
+```php
+private const DIRECTIONS = array( 'push', 'pull' );
+private const CONTENT_TYPES = array( 'posts', 'terms', 'media', 'custom_post_types' );
+private const MIN_LOGS = 10;
+private const MAX_LOGS = 1000;
+
+public function logRetention(): int;
+public function debugLoggingEnabled(): bool;
+```
+
+Update `sanitizeSyncPairs()` so each pair returns:
+
+```php
+array(
+ 'name' => $name,
+ 'source_url' => $source_url,
+ 'destination_url' => $destination_url,
+ 'username' => $username,
+ 'application_password' => $application_password,
+ 'default_direction' => $direction,
+ 'content_types' => $content_types,
+ 'url_mappings' => $url_mappings,
+)
+```
+
+Rules:
+- Preserve legacy `name`, `source_url`, and `destination_url` behavior.
+- Sanitize `username` with `sanitize_text_field()`.
+- Sanitize `application_password` with `sanitize_text_field()` and never log it.
+- Keep only allowed content types.
+- Keep only URL mappings where both source and destination are non-empty URLs.
+- Clamp log retention between `10` and `1000`.
+- Default direction to `push`.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter SettingsTest`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Settings/Settings.php tests/Unit/SettingsTest.php
+git commit -m "feat: expand admin sync settings"
+```
+
+---
+
+## Task 2: Admin Notices
+
+**Files:**
+- Create: `src/Admin/AdminNotice.php`
+- Create: `src/Admin/AdminNoticeRepository.php`
+- Create: `tests/Unit/Admin/AdminNoticeRepositoryTest.php`
+
+- [ ] **Step 1: Write failing notice tests**
+
+Create `tests/Unit/Admin/AdminNoticeRepositoryTest.php`:
+
+```php
+current();
+
+ self::assertSame( 'success', $notices[0]->type() );
+ self::assertSame( 'The package JSON file was imported successfully.', $notices[0]->message() );
+ }
+
+ public function test_it_sanitizes_error_notices(): void {
+ $_GET['wpcs_import_error'] = '';
+ $notices = ( new AdminNoticeRepository() )->current();
+
+ self::assertSame( 'error', $notices[0]->type() );
+ self::assertSame( 'Bad package', $notices[0]->message() );
+ }
+}
+```
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter AdminNoticeRepositoryTest`
+
+Expected: FAIL with missing classes.
+
+- [ ] **Step 3: Implement notices**
+
+Create `AdminNotice` with:
+
+```php
+public function __construct( string $type, string $message );
+public function type(): string;
+public function message(): string;
+```
+
+Allowed types: `success`, `warning`, `error`, `info`; fallback to `info`.
+
+Create `AdminNoticeRepository::current(): array` returning notices for:
+- `wpcs_imported=1`: success, imported successfully.
+- `wpcs_import_error`: sanitized error message.
+- `wpcs_settings_saved=1`: success, settings saved.
+- `wpcs_connection_ok=1`: success, REST connection succeeded.
+- `wpcs_connection_error`: sanitized error message.
+- `wpcs_logs_cleared=1`: success, logs cleared.
+- `wpcs_export_error`: sanitized error message.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter AdminNoticeRepositoryTest`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Admin/AdminNotice.php src/Admin/AdminNoticeRepository.php tests/Unit/Admin/AdminNoticeRepositoryTest.php
+git commit -m "feat: add admin notices"
+```
+
+---
+
+## Task 3: Settings Save Controller
+
+**Files:**
+- Create: `src/Admin/SettingsController.php`
+- Modify: `src/Plugin.php`
+- Create: `tests/Unit/Admin/SettingsControllerTest.php`
+- Modify: `tests/Unit/PluginTest.php`
+
+- [ ] **Step 1: Write failing controller tests**
+
+Create `tests/Unit/Admin/SettingsControllerTest.php`:
+
+```php
+ 'debug',
+ 'conflict_strategy' => 'manual_review',
+ );
+
+ ( new SettingsController( new SettingsRepository() ) )->handleSave();
+
+ self::assertSame( 'debug', $GLOBALS['wpcs_test_options'][ SettingsRepository::OPTION_NAME ]['logging_level'] );
+ self::assertStringContainsString( 'wpcs_settings_saved=1', $GLOBALS['wpcs_redirect_location'] );
+ }
+}
+```
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter SettingsControllerTest`
+
+Expected: FAIL with missing `SettingsController`.
+
+- [ ] **Step 3: Implement controller**
+
+Create `SettingsController`:
+
+```php
+final class SettingsController {
+ private SettingsRepository $settings_repository;
+
+ public function __construct( SettingsRepository $settings_repository ) {}
+ public function register(): void;
+ public function handleSave(): void;
+}
+```
+
+Behavior:
+- `register()` hooks `admin_post_wpcs_save_settings`.
+- `handleSave()` requires `manage_options`.
+- Verify nonce `wpcs_save_settings` / `wpcs_settings_nonce`.
+- Read `$_POST['wpcs_settings']`, unslash with `wp_unslash()`, require array.
+- Persist through `SettingsRepository::save( Settings::fromArray( $data ) )`.
+- Redirect to dashboard with `wpcs_settings_saved=1`.
+
+Update `Plugin::create()` to register `SettingsController`.
+Update `Plugin::register()` to call `SettingsController::register()`.
+Update `PluginTest` to assert the service exists and the hook is registered.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter "SettingsControllerTest|PluginTest"`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Admin/SettingsController.php src/Plugin.php tests/Unit/Admin/SettingsControllerTest.php tests/Unit/PluginTest.php
+git commit -m "feat: save admin sync settings"
+```
+
+---
+
+## Task 4: Dashboard Settings Form
+
+**Files:**
+- Modify: `templates/admin/dashboard.php`
+- Modify: `src/Admin/AdminPage.php`
+- Modify: `tests/Unit/Admin/DashboardTemplateTest.php`
+
+- [ ] **Step 1: Write failing dashboard tests**
+
+Add tests:
+
+```php
+public function test_it_renders_settings_form_with_nonce_and_escaped_pair_values(): void {
+ $settings = Settings::fromArray(
+ array(
+ 'sync_pairs' => array(
+ array(
+ 'name' => 'Staging',
+ 'source_url' => 'https://example.test',
+ 'destination_url' => 'https://staging.example.test',
+ 'username' => 'codex',
+ ),
+ ),
+ )
+ );
+
+ $output = $this->renderDashboard( $settings );
+
+ self::assertStringContainsString( 'action="https://example.test/wp-admin/admin-post.php"', $output );
+ self::assertStringContainsString( 'name="action" value="wpcs_save_settings"', $output );
+ self::assertStringContainsString( 'Staging', $output );
+ self::assertStringNotContainsString( 'Staging', $output );
+ self::assertStringContainsString( 'name="wpcs_settings[sync_pairs][0][application_password]"', $output );
+}
+```
+
+Update `renderDashboard()` to accept optional `Settings $settings`.
+
+- [ ] **Step 2: Run test to verify failure**
+
+Run: `composer test -- --filter DashboardTemplateTest`
+
+Expected: FAIL because the dashboard does not render the settings form.
+
+- [ ] **Step 3: Implement dashboard form**
+
+Update `dashboard.php`:
+- Replace the foundation notice with a "Configuration" section.
+- Render a form posting to `admin-post.php`.
+- Include `action=wpcs_save_settings`.
+- Include nonce `wp_nonce_field( 'wpcs_save_settings', 'wpcs_settings_nonce' )`.
+- Render existing sync pairs; if none exist, render one blank pair row.
+- Fields per pair: name, source URL, destination URL, username, application password, default direction, content types, URL mappings.
+- Never render saved application password values back into the password field; render placeholder text only.
+- Escape every attribute with `esc_attr()` and every text node with `esc_html()`.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter DashboardTemplateTest`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add templates/admin/dashboard.php src/Admin/AdminPage.php tests/Unit/Admin/DashboardTemplateTest.php
+git commit -m "feat: render admin settings workflow"
+```
+
+---
+
+## Task 5: Connection Diagnostics
+
+**Files:**
+- Create: `src/Admin/ConnectionTestController.php`
+- Modify: `src/Plugin.php`
+- Create: `tests/Unit/Admin/ConnectionTestControllerTest.php`
+- Modify: `tests/Unit/Admin/DashboardTemplateTest.php`
+- Modify: `templates/admin/dashboard.php`
+
+- [ ] **Step 1: Write failing diagnostics tests**
+
+Create `ConnectionTestControllerTest` that injects `RestTransportClient`, posts `pair_index=0`, and verifies:
+- capability and nonce are required.
+- success redirects with `wpcs_connection_ok=1`.
+- failures redirect with `wpcs_connection_error`.
+- application password is never placed in redirect query args.
+
+Use settings fixture:
+
+```php
+update_option(
+ SettingsRepository::OPTION_NAME,
+ array(
+ 'sync_pairs' => array(
+ array(
+ 'name' => 'Staging',
+ 'destination_url' => 'https://destination.test',
+ 'username' => 'codex',
+ 'application_password' => 'app-pass',
+ ),
+ ),
+ ),
+ false
+);
+```
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter ConnectionTestControllerTest`
+
+Expected: FAIL with missing controller.
+
+- [ ] **Step 3: Implement diagnostics controller**
+
+Create `ConnectionTestController`:
+- Hook `admin_post_wpcs_test_connection`.
+- Require `manage_options`.
+- Verify nonce `wpcs_test_connection` / `wpcs_connection_nonce`.
+- Read `pair_index` as integer.
+- Load selected pair from settings.
+- Call `RestTransportClient::testConnection( destination_url, username, application_password )`.
+- On success redirect with `wpcs_connection_ok=1`.
+- On `RestTransportException` redirect with sanitized `wpcs_connection_error`.
+- Log success/failure without credentials.
+
+Update `Plugin` to register the controller.
+Update dashboard to add a "Test REST Connection" button per pair with nonce.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter "ConnectionTestControllerTest|DashboardTemplateTest|PluginTest"`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Admin/ConnectionTestController.php src/Plugin.php templates/admin/dashboard.php tests/Unit/Admin/ConnectionTestControllerTest.php tests/Unit/Admin/DashboardTemplateTest.php tests/Unit/PluginTest.php
+git commit -m "feat: add connection diagnostics"
+```
+
+---
+
+## Task 6: REST Transport Rejected Body Handling
+
+**Files:**
+- Modify: `src/Transport/RestTransportClient.php`
+- Modify: `tests/Unit/Transport/RestTransportClientTest.php`
+
+- [ ] **Step 1: Write failing REST client test**
+
+Add:
+
+```php
+public function test_it_throws_when_receive_endpoint_returns_accepted_false(): void {
+ $GLOBALS['wpcs_http_response'] = array(
+ 'response' => array( 'code' => 200 ),
+ 'body' => '{"accepted":false,"import":{"errors":["Posts failed."]}}',
+ );
+ $client = new RestTransportClient();
+
+ $this->expectException( RestTransportException::class );
+ $this->expectExceptionMessage( 'Posts failed.' );
+
+ $client->sendPackage( 'https://destination.test', 'codex', 'app-pass', $this->package() );
+}
+```
+
+- [ ] **Step 2: Run test to verify failure**
+
+Run: `composer test -- --filter RestTransportClientTest`
+
+Expected: FAIL because HTTP 200 currently counts as success.
+
+- [ ] **Step 3: Implement body inspection**
+
+Update `assertSuccessfulResponse()`:
+- If status code is expected, decode body.
+- If decoded body has `accepted === false`, throw `RestTransportException::remoteRejected()`.
+- Prefer first string in `import.errors`, then first string in `errors`, then `message`, then fallback.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter RestTransportClientTest`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Transport/RestTransportClient.php tests/Unit/Transport/RestTransportClientTest.php
+git commit -m "fix: reject failed rest imports"
+```
+
+---
+
+## Task 7: Operation Logs and Clear Action
+
+**Files:**
+- Modify: `src/Logging/OptionLogger.php`
+- Create: `src/Admin/LogController.php`
+- Modify: `src/Plugin.php`
+- Modify: `templates/admin/dashboard.php`
+- Modify: `tests/Unit/OptionLoggerTest.php`
+- Create: `tests/Unit/Admin/LogControllerTest.php`
+- Modify: `tests/Unit/Admin/DashboardTemplateTest.php`
+
+- [ ] **Step 1: Write failing logger/controller tests**
+
+Add `OptionLoggerTest` coverage for:
+
+```php
+$logger = new OptionLogger( 10 );
+$logger->info( 'Imported content package.' );
+self::assertCount( 1, $logger->entries() );
+$logger->clear();
+self::assertSame( array(), $logger->entries() );
+```
+
+Create `LogControllerTest` verifying:
+- capability and nonce are required.
+- `handleClear()` clears `OptionLogger::OPTION_NAME`.
+- redirect contains `wpcs_logs_cleared=1`.
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter "OptionLoggerTest|LogControllerTest"`
+
+Expected: FAIL with missing methods/controller.
+
+- [ ] **Step 3: Implement logs**
+
+Update `OptionLogger`:
+
+```php
+public function entries(): array;
+public function clear(): void;
+```
+
+Create `LogController`:
+- Hook `admin_post_wpcs_clear_logs`.
+- Require `manage_options`.
+- Verify nonce `wpcs_clear_logs` / `wpcs_logs_nonce`.
+- Clear logs.
+- Redirect with `wpcs_logs_cleared=1`.
+
+Update dashboard:
+- Render a recent operation history table.
+- Show timestamp, level, message, and redacted context summary.
+- Add "Clear Logs" form with nonce.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter "OptionLoggerTest|LogControllerTest|DashboardTemplateTest|PluginTest"`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Logging/OptionLogger.php src/Admin/LogController.php src/Plugin.php templates/admin/dashboard.php tests/Unit/OptionLoggerTest.php tests/Unit/Admin/LogControllerTest.php tests/Unit/Admin/DashboardTemplateTest.php tests/Unit/PluginTest.php
+git commit -m "feat: add operation log controls"
+```
+
+---
+
+## Task 8: File Export Scaffold
+
+**Files:**
+- Create: `src/Admin/FileExportController.php`
+- Modify: `src/Plugin.php`
+- Modify: `templates/admin/dashboard.php`
+- Create: `tests/Unit/Admin/FileExportControllerTest.php`
+- Modify: `tests/Unit/Admin/DashboardTemplateTest.php`
+
+- [ ] **Step 1: Write failing export tests**
+
+Create `FileExportControllerTest` verifying:
+- capability and nonce are required.
+- a configured pair exports a valid JSON package with all four record buckets.
+- response headers include `Content-Type: application/json`.
+- no content mutation occurs during export.
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter FileExportControllerTest`
+
+Expected: FAIL with missing controller.
+
+- [ ] **Step 3: Implement export scaffold**
+
+Create `FileExportController`:
+- Hook `admin_post_wpcs_export_package`.
+- Require `manage_options`.
+- Verify nonce `wpcs_export_package` / `wpcs_export_nonce`.
+- Read `pair_index`.
+- Build an empty `ContentPackage` using the selected pair:
+ - `source.site_url` from pair source URL.
+ - `destination.site_url` from pair destination URL.
+ - manifest counts all zero.
+ - records buckets all empty.
+ - checksum from `PackageChecksum::records()`.
+- Export with `JsonFileTransport::export()`.
+- Send JSON download headers.
+
+Update dashboard with an "Export Empty Package" form and helper copy stating full extraction will be added in a later slice.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter "FileExportControllerTest|DashboardTemplateTest|PluginTest"`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Admin/FileExportController.php src/Plugin.php templates/admin/dashboard.php tests/Unit/Admin/FileExportControllerTest.php tests/Unit/Admin/DashboardTemplateTest.php tests/Unit/PluginTest.php
+git commit -m "feat: add package export scaffold"
+```
+
+---
+
+## Task 9: Import Result UI Hardening
+
+**Files:**
+- Modify: `src/Admin/FileImportController.php`
+- Modify: `templates/admin/dashboard.php`
+- Modify: `tests/Unit/Admin/FileImportControllerTest.php`
+- Modify: `tests/Unit/Admin/DashboardTemplateTest.php`
+
+- [ ] **Step 1: Write failing result tests**
+
+Add file import test asserting success redirects include counts:
+
+```php
+self::assertStringContainsString( 'wpcs_created=0', $GLOBALS['wpcs_redirect_location'] );
+self::assertStringContainsString( 'wpcs_updated=0', $GLOBALS['wpcs_redirect_location'] );
+self::assertStringContainsString( 'wpcs_skipped=0', $GLOBALS['wpcs_redirect_location'] );
+self::assertStringContainsString( 'wpcs_conflicts=0', $GLOBALS['wpcs_redirect_location'] );
+```
+
+Add dashboard test asserting the success notice includes created/updated/skipped/conflict counts from sanitized query args.
+
+- [ ] **Step 2: Run tests to verify failure**
+
+Run: `composer test -- --filter "FileImportControllerTest|DashboardTemplateTest"`
+
+Expected: FAIL because result counts are not in redirects or UI.
+
+- [ ] **Step 3: Implement result count redirects**
+
+Update `FileImportController` success redirect:
+
+```php
+array(
+ 'wpcs_imported' => '1',
+ 'wpcs_created' => (string) $result->created(),
+ 'wpcs_updated' => (string) $result->updated(),
+ 'wpcs_skipped' => (string) $result->skipped(),
+ 'wpcs_conflicts' => (string) $result->conflicts(),
+)
+```
+
+Update dashboard to display counts with `absint()` and escaped labels.
+
+- [ ] **Step 4: Run focused tests**
+
+Run: `composer test -- --filter "FileImportControllerTest|DashboardTemplateTest"`
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/Admin/FileImportController.php templates/admin/dashboard.php tests/Unit/Admin/FileImportControllerTest.php tests/Unit/Admin/DashboardTemplateTest.php
+git commit -m "feat: show import result summaries"
+```
+
+---
+
+## Task 10: Smoke Checklist Documentation
+
+**Files:**
+- Create: `docs/smoke/phase-6-admin-hardening.md`
+
+- [ ] **Step 1: Create smoke checklist**
+
+Create `docs/smoke/phase-6-admin-hardening.md` with:
+
+```markdown
+# Phase 6 Admin Hardening Smoke Checklist
+
+## Environment
+
+- WordPress site URL:
+- Plugin branch/commit:
+- PHP version:
+- WordPress version:
+
+## Checks
+
+- [ ] Plugin activates without fatal errors.
+- [ ] Tools -> Content Sync loads for an administrator.
+- [ ] Non-administrators cannot access the dashboard.
+- [ ] Settings save rejects missing/invalid nonce.
+- [ ] Settings save persists sync pair name, URLs, username, content types, direction, and URL mappings.
+- [ ] Saved application password is not rendered back into the password field.
+- [ ] REST status endpoint rejects unauthenticated HTTP requests.
+- [ ] REST status endpoint accepts authenticated requests when the server passes `Authorization` to PHP.
+- [ ] Connection test succeeds with a valid application password.
+- [ ] Connection test shows an actionable error for invalid credentials.
+- [ ] Invalid package file import redirects with an error notice.
+- [ ] Valid empty package import redirects with success and result counts.
+- [ ] REST package POST accepts a valid package and includes import counts.
+- [ ] REST package POST rejects invalid package data.
+- [ ] Operation log table shows recent events with redacted credential-like fields.
+- [ ] Clear logs requires nonce and removes log entries.
+- [ ] Export scaffold downloads valid JSON.
+
+## Local Notes
+
+- On Herd/nginx, direct HTTP Basic auth may require forwarding the `Authorization` header. If HTTP application-password smoke returns 401 while internal REST dispatch passes, verify server auth header configuration before treating it as a plugin failure.
+```
+
+- [ ] **Step 2: Commit docs**
+
+```bash
+git add docs/smoke/phase-6-admin-hardening.md
+git commit -m "docs: add admin hardening smoke checklist"
+```
+
+---
+
+## Task 11: Full Phase 6 Verification
+
+**Files:**
+- Verify all files changed in Tasks 1-10.
+
+- [ ] **Step 1: Run Composer validation**
+
+Run: `composer validate --strict`
+
+Expected: PASS with `./composer.json is valid`.
+
+- [ ] **Step 2: Run PHPCS**
+
+Run: `composer lint`
+
+Expected: PASS with no PHPCS errors or warnings.
+
+- [ ] **Step 3: Run PHPStan**
+
+Run: `vendor\bin\phpstan analyse --memory-limit=1G`
+
+Expected: PASS with `[OK] No errors`.
+
+- [ ] **Step 4: Run PHPUnit**
+
+Run: `composer test`
+
+Expected: PASS with all unit tests passing.
+
+- [ ] **Step 5: Copy runtime files to Herd test plugin**
+
+Run:
+
+```powershell
+Copy-Item -Path src,templates,wp-content-sync.php,composer.json -Destination 'C:\Users\ksolo\Herd\basic-wp\wp-content\plugins\WP-Content-Sync' -Recurse -Force
+```
+
+Expected: command exits 0.
+
+- [ ] **Step 6: Run manual smoke checklist**
+
+Use `docs/smoke/phase-6-admin-hardening.md`.
+
+Required checks before completion:
+- Plugin active.
+- Admin dashboard loads after login.
+- Settings save works and rejects invalid nonce.
+- Connection diagnostics show success/failure notices.
+- Invalid file import redirects with error.
+- Valid empty file import redirects with success counts.
+- REST unauthenticated status returns 401.
+- REST valid and invalid package behavior is verified by HTTP application password if server passes Basic auth, otherwise by internal REST dispatch plus documented server caveat.
+- Logs render and clear.
+- Export scaffold downloads valid JSON.
+
+- [ ] **Step 7: Commit verification notes if changed**
+
+If smoke notes are updated with environment results:
+
+```bash
+git add docs/smoke/phase-6-admin-hardening.md
+git commit -m "docs: record phase 6 smoke results"
+```
+
+---
+
+## Spec Coverage
+
+- Sync pair configuration is covered by Tasks 1, 3, and 4.
+- Credentials and authentication setup are covered by Tasks 1, 4, and 5, with password fields not rendered back to the browser.
+- URL mapping configuration is covered by Task 1 and Task 4.
+- Content type selection and default sync direction are covered by Task 1 and Task 4.
+- Connection diagnostics are covered by Task 5.
+- Import screens and user-facing import errors are covered by Tasks 2, 4, and 9.
+- Export screen is covered by Task 8 as a valid package scaffold until full extraction is implemented.
+- Operation history and debug/log controls are covered by Task 7.
+- REST failure hardening is covered by Task 6.
+- Nonces/capabilities for state-changing admin actions are covered by Tasks 3, 5, 7, 8, and existing file import tests.
+- Final smoke/integration checklist is covered by Tasks 10 and 11.
+
+## Deferred Work
+
+- Full content extraction for non-empty package exports remains a later slice because Phase 5 built import orchestration and handlers, not source extraction orchestration.
+- Background queues, progress polling, and cancelable long-running operations remain a later scalability slice.
+- HTTP Basic authentication pass-through is a server configuration concern; plugin REST behavior remains covered by controller tests and internal REST dispatch smoke.
+
+## Placeholder Scan
+
+- No unresolved placeholder markers are intentionally included.
+- Each task names exact files, expected failing tests, implementation behavior, verification commands, and commit messages.
+- Deferred work is explicitly scoped with rationale and is not required for Phase 6 exit criteria in this implementation slice.