# URL Manager — Improvement & Growth Plan

> Package: capell-app/url-manager · Kind: package · Tier: premium · Product group: Capell Search & SEO · Bundle: search-seo · Status: Draft

## 1. Snapshot

URL Manager is an action-driven redirect engine for Capell. It surfaces two admin Filament pages (`RedirectRulesPage`, `NotFoundOpportunitiesPage`) and decorates Core's `RedirectResolver` binding on the public request path via `UrlManagerRedirectResolver`. Core domain logic lives in `src/Actions` — `ResolveRedirectRuleAction` (exact/prefix/regex matching + hit recording), `UpsertRedirectRuleAction`, `RecordNotFoundOpportunityAction`, `ConvertNotFoundOpportunityToRedirectAction`, the CSV import/export family, `BuildNotFoundRedirectSuggestionsAction`, and `ImportSeoSuiteBrokenLinksAction`. Three models/tables back it: `url_manager_redirect_rules`, `url_manager_redirect_hits`, `url_manager_not_found_opportunities`. Deps: `capell-app/core`, `capell-app/admin`, `lorisleiva/laravel-actions`, `spatie/laravel-data`; soft-supports `capell-app/seo-suite`.

Current marketplace summary (verbatim): _"Manage redirects, preserve moved URLs, track redirect hits, and turn repeated 404s or SEO Suite broken URL findings into redirect opportunities."_ Current screenshot-quality follow-up demotes the 8 existing `docs/screenshots/*` PNGs from `capell.json` because visual audit showed the captures are dominated by a broken default shell/oversized black arc rather than usable Capell UI. `docs/screenshots.json` still defines the runner contract so the redirect workflows can be recaptured and re-promoted after the runner loads the correct assets.

## 2. Improvements (existing functionality)

- **Done/Shipped: Lowercase / canonicalise the matched path in normalisation** — `NormalizeManagedUrlAction::handle()` lowercases managed paths before hashing and resolution. Evidence: `tests/Unit/Actions/RedirectRuleActionsTest.php` covers `/Old-Page/` resolving from `/old-page?utm_source=test`. — `src/Actions/NormalizeManagedUrlAction.php` — S
- **Done/Shipped: Strip the query string before hashing for match (keep it only for `preserve_query` reattachment)** — `NormalizeManagedUrlAction::pathFromUrl()` returns only `PHP_URL_PATH`; query reattachment remains in `UrlManagerRedirectResolver::targetUrl()`. Evidence: `tests/Unit/Actions/RedirectRuleActionsTest.php` and `tests/Unit/Filament/RedirectRulesPageTest.php` cover query-free matching and preserve-query response behavior. — `src/Actions/NormalizeManagedUrlAction.php`, consumed by `ResolveRedirectRuleAction::findExactRule()` — M
- **Done/Shipped: Make hot-path hit recording asynchronous / deferrable** — `ResolveRedirectRuleAction::recordHit()` defers redirect hit writes to an application `terminating()` callback by default via `capell-url-manager.hit_recording.defer`, while keeping synchronous recording available for tests/explicit callers. `RecordRedirectHitAction` updates rule counters atomically when the deferred callback runs. Evidence: `tests/Unit/Actions/RedirectRuleActionsTest.php` covers resolving a redirect without any immediate `redirect_hits` row or `hit_count` update, then recording after `app()->terminate()`. — `src/Actions/ResolveRedirectRuleAction.php`, `src/Actions/RecordRedirectHitAction.php`, `config/capell-url-manager.php` — M
- **Done/Shipped: avoid unbounded prefix/regex scans.** — `findPrefixRule()` builds concrete path candidates and resolves them with `whereIn(...)`, priority, and longest-source ordering; `findRegexRule()` is priority ordered and capped by `capell-url-manager.redirects.regex.max_rules_checked`. — `src/Actions/ResolveRedirectRuleAction.php`, `config/capell-url-manager.php` — M
- **Done/Shipped: add a `priority`/ordering column to `redirect_rules`.** — Redirect rules persist `priority`, the form exposes bounded priority editing, the table displays/sorts it, and resolver order honors priority for exact/prefix/regex paths. — `database/migrations/2026_05_31_000001_create_url_manager_redirect_rules_table.php`, `database/migrations/2026_06_04_000001_add_priority_to_url_manager_redirect_rules_table.php`, `src/Actions/ResolveRedirectRuleAction.php`, `src/Filament/Pages/Schemas/RedirectRuleForm.php` — M
- **Done/Shipped: form-level validation parity with the Action.** — `RedirectRuleForm` calls `PrepareRedirectRuleDataAction` from Filament validation closures so invalid regex, disallowed status codes, absolute target host rejection, self redirects, and loop checks surface before save. — `src/Filament/Pages/Schemas/RedirectRuleForm.php`, `src/Actions/PrepareRedirectRuleDataAction.php` — S
- **Done/Shipped: index `redirect_hits.hit_at` / rule FK for analytics queries.** — Redirect hits now have a compound `redirect_rule_id, hit_at` index, and `PruneRedirectHitsAction` / `capell:url-manager-prune-hits` prune old hit rows using the configured retention window. — `database/migrations/2026_05_31_000002_create_url_manager_redirect_hits_table.php`, `src/Actions/PruneRedirectHitsAction.php`, `src/Console/Commands/PruneRedirectHitsCommand.php` — S

## 3. Missing Features (gaps)

Mapped to `capabilities[]` in `capell.json`:

- **Done/Shipped: redirect loop & chain detection.** — `PrepareRedirectRuleDataAction::resolveFinalTargetUrl()` follows active exact-rule chains up to the configured `max_chain_depth`, rejects cycles, and collapses intermediate exact targets to their final target. — `src/Actions/PrepareRedirectRuleDataAction.php`, `config/capell-url-manager.php`
- **Done/Shipped: 404 capture is wired in-repo.** — `UrlManagerServiceProvider` appends `RecordNotFoundOpportunityMiddleware` to the frontend route middleware registry, and the middleware records non-ignored 404 responses as opportunities with site/language context. — `src/Providers/UrlManagerServiceProvider.php`, `src/Http/Middleware/RecordNotFoundOpportunityMiddleware.php`
- **Done/Shipped: Resolver auto-wiring verification.** — Capell Core binds `RedirectResolver::class` to `PageUrlRedirectResolver`, Capell Frontend invokes `resolve(RedirectResolver::class)->resolve(...)` in `ResolvePublicPageRequestAction` before returning public pages, and URL Manager decorates the bound resolver when installed. Package coverage verifies the decorator preserves Core decisions and falls back to managed redirects. — `../capell-4/packages/core/src/Providers/CapellServiceProvider.php`, `../capell-4/packages/frontend/src/Actions/ResolvePublicPageRequestAction.php`, `src/Providers/UrlManagerServiceProvider.php`, `tests/Unit/Actions/RedirectIntegrationActionsTest.php`
- **Done/Shipped: auto slug-change redirects beyond exact.** `RecordChangedUrlRedirectAction` creates the exact redirect for a changed page URL and, when `create_prefix_redirect_on_parent_move` is enabled, also records a prefix redirect for parent moves so child URLs continue to resolve. — `src/Actions/RecordChangedUrlRedirectAction.php`, `config/capell-url-manager.php`, `tests/Unit/Actions/RedirectRuleActionsTest.php`
- **Done/Shipped: canonical URL management policy.** `BuildCanonicalUrlAction` provides the URL Manager canonical policy: force scheme/host when configured, lowercase paths, add/remove trailing slashes, and strip configured tracking query keys. The config exposes the policy and docs identify it as a callable integration surface rather than automatic tag rendering. — `src/Actions/BuildCanonicalUrlAction.php`, `config/capell-url-manager.php`, `docs/overview.md`, `tests/Unit/Actions/RedirectRuleActionsTest.php`
- **Done/Shipped: bulk operations in admin.** — `RedirectRulesTable` wires bulk activate, bulk disable, and bulk delete actions, plus import, preview-import, export, and template-download header actions. — `src/Filament/Pages/Tables/RedirectRulesTable.php`
- **301 vs 302 guidance / defaults per source** — status code is a free Select (301/302/307/308) with no guidance; auto-suggest 301 for permanent moves, 302 for temporary, and warn on 302 for slug-change redirects. — table-stakes polish
- **Done/Shipped: Open-redirect allowlist for absolute targets** — `PrepareRedirectRuleDataAction` validates absolute target hosts against `capell-url-manager.redirects.absolute_target_allowed_hosts` plus the configured `app.url` host when enabled. Evidence: `tests/Unit/Actions/RedirectRuleActionsTest.php` covers rejected, allowed, update, and import-preview paths.
- **Done/Shipped: Health check implementation** — `UrlManagerHealthCheck` now checks required tables, manifest-declared action classes, runtime/admin provider metadata, and required table metadata. Evidence: `tests/Unit/Health/UrlManagerHealthCheckTest.php`.

## 4. Issues / Risks

- **Done/Shipped: Health check is a stub** — resolved by `UrlManagerHealthCheck::runDiagnostics()`, which returns table, action class, and provider metadata diagnostics and fails missing table/action/provider metadata cases. Evidence: `tests/Unit/Health/UrlManagerHealthCheckTest.php`. — `src/Health/UrlManagerHealthCheck.php`, `capell.json` healthChecks
- **Done/Shipped: Open-redirect surface** — resolved by `PrepareRedirectRuleDataAction::assertAllowedTargetUrl()`, backed by configurable absolute target host allowlists and import/update coverage. Evidence: `tests/Unit/Actions/RedirectRuleActionsTest.php`. — `src/Actions/PrepareRedirectRuleDataAction.php`, `config/capell-url-manager.php`
- **Done/Shipped: untrusted regex patterns are validated and bounded before save.** — `PrepareRedirectRuleDataAction::normalizeRegexSource()` trims regex sources, rejects empty, invalid, or overlong patterns using `capell-url-manager.redirects.regex.max_pattern_length`, and resolver checks are capped by `max_rules_checked`. — `src/Actions/PrepareRedirectRuleDataAction.php`, `src/Actions/ResolveRedirectRuleAction.php`, `config/capell-url-manager.php`
- **Done/Shipped: loop detection** — see §3; save-time exact-rule chain traversal rejects cyclic redirect sets before they can be served to the public.
- **Done/Shipped: Synchronous double-write per redirect** — resolved by deferring hit-row creation and rule counter updates to an application termination callback by default, so public redirect resolution can return the redirect decision before hit writes execute. Evidence: `tests/Unit/Actions/RedirectRuleActionsTest.php` covers no immediate hit row or counter update before `app()->terminate()`.
- **No result caching of resolved rules** — exact-match lookups by `source_hash` are cheap but prefix/regex resolution is O(rows) in PHP per request with no memoisation. Manifest `adminQueryBudget: 40` covers admin, but there is no frontend query budget enforced.
- **Done/Shipped: Test coverage gaps reconciled.** — Existing package coverage now proves fallback chaining/Core-decision precedence, managed resolver preserve-query reattachment, open-redirect rejection for create/update/import preview, loop/cycle rejection, SEO Suite broken-link import, health diagnostics, and manifest requirements. URL Manager has no public Blade output surface, so anonymous/non-admin output-safety coverage is not applicable for this package. — `tests/Unit/Actions/RedirectIntegrationActionsTest.php`, `tests/Unit/Actions/RedirectRuleActionsTest.php`, `tests/Unit/Actions/NotFoundOpportunityActionsTest.php`, `tests/Unit/Filament/RedirectRulesPageTest.php`, `tests/Unit/Health/UrlManagerHealthCheckTest.php`, `tests/Unit/ManifestRequirementsTest.php`
- **Done/Shipped: Action exception messages are translated.** — URL normalization, redirect validation, import preview/import failures, CSV parsing, and 404 opportunity conversion now raise package translation strings via `capell-url-manager::validation.*`. — `src/Actions/PrepareRedirectRuleDataAction.php`, `src/Actions/ImportRedirectRulesAction.php`, `src/Actions/PreviewRedirectRulesImportAction.php`, `src/Actions/ConvertNotFoundOpportunityToRedirectAction.php`, `src/Actions/ParseRedirectRulesCsvAction.php`, `resources/lang/en/validation.php`
- **Done/Shipped: config file added.** — `config/capell-url-manager.php` now exposes status-code allowlist, absolute target hosts, redirect chain depth, regex bounds, prefix redirect behavior, deferred hit recording, hit retention, 404 capture settings, ignored 404 prefixes, and canonical URL behavior; the service provider publishes it via `hasConfigFile('capell-url-manager')`.

## 5. Marketplace & Selling

**Critique.** The manifest `summary` is accurate but reads as a feature list, not a value statement, and buries the strongest hook (recovering lost SEO traffic). The package manifest now uses recovery-led marketplace copy, but the previously promoted 8 screenshots are demoted until they are recaptured as styled Capell UI instead of broken default-shell output.

**Improved 1-sentence summary:** _Stop losing traffic to broken links — manage redirects, auto-preserve moved page URLs, and turn repeated 404s into recovered SEO._

**Improved 3–4 sentence description:** _URL Manager keeps your site's link equity intact when pages move or get renamed. It auto-creates redirects when a page URL changes, resolves exact, prefix, and regex rules on the live request, and tracks hit counts so you can see which redirects matter. Repeated 404s and SEO Suite broken-link findings surface as one-click redirect opportunities, with CSV import/export for bulk migrations. Built for editors and SEO teams who need redirect hygiene without touching server config._

**Screenshot/media gaps:** Reopened. The existing `docs/screenshots/*` set (create/edit form, import workflow, export workflow, health snapshot) and matching Capell runner contract remain useful as target definitions, but the committed captures are not buyer-facing quality and are no longer promoted in `capell.json`. Recapture styled light/dark redirect workflows before marking marketplace media complete. Still missing: the 404 Opportunities table with a convert action, and a redirect-hit analytics view (once §2 analytics lands).

**Pricing / tier / bundle positioning:** `premium` tier inside the `search-seo` bundle is right. Cross-sell is the lever: it `supports` `seo-suite` and consumes its `BrokenLink` rows, so position as the _remediation_ half of an SEO loop (SEO Suite finds, URL Manager fixes). Strong fit with the **migration-assistant** and **wordpress-importer** Extension Suites — bulk redirect import is exactly what platform migrations need; surface `ImportRedirectRulesAction` as their redirect-mapping target. Bundle it as the default redirect layer whenever migration-assistant is purchased.

**Differentiators / value props / target buyer:** differentiator is the closed loop — automatic slug-change capture + 404 mining + SEO Suite import feeding suggested redirects, all inside the CMS. Target buyer: SEO managers and content teams on multi-site Capell installs who run frequent content reorganisations and migrations.

**Keywords/tags:** `redirects`, `301 redirect`, `404 management`, `url management`, `seo`, `link equity`, `slug change`, `redirect import`, `broken links`, `site migration`, `regex redirect`, `multi-site`.

## 6. Prioritized Roadmap

| Item                                                                                            | Bucket | Effort | Impact | Section ref                                                                             |
| ----------------------------------------------------------------------------------------------- | ------ | ------ | ------ | --------------------------------------------------------------------------------------- |
| Done/Shipped: Implement real `UrlManagerHealthCheck` (tables/actions/provider discoverable)     | Done   | S      | High   | §4                                                                                      |
| Done/Shipped 2026-06-08: Add managed `410 Gone` rules for bulk/imported URL removals            | Done   | M      | High   | §3 — empty-target imports, form validation, resolver filtering, and frontend middleware |
| Recapture styled marketplace screenshots from the existing `docs/screenshots/*` runner contract | Later  | S      | High   | §5 — deferred until styled runner recapture; no implementation blocker                  |
| Done/Shipped: Strip query from match key; lowercase normalisation                               | Done   | M      | High   | §2                                                                                      |
| Done/Shipped: Open-redirect host allowlist for absolute targets                                 | Done   | M      | High   | §3, §4                                                                                  |
| Done/Shipped: Defer hot-path hit recording (queue / terminating)                                | Done   | M      | High   | §2, §4                                                                                  |
| Done/Shipped: Validate & bound regex patterns at write time (ReDoS)                             | Done   | M      | High   | §4                                                                                      |
| Done/Shipped: Redirect loop & chain detection on save                                           | Done   | M      | High   | §3                                                                                      |
| Done/Shipped: Wire 404 capture into frontend route middleware                                   | Done   | M      | High   | §3                                                                                      |
| Done/Shipped: Verify/confirm Core invokes the bound `RedirectResolver`                          | Done   | S      | High   | §3, §4                                                                                  |
| Done/Shipped: Add `priority` ordering column + resolver/form support                            | Done   | M      | Med    | §2, §3                                                                                  |
| Done/Shipped: Replace unbounded prefix/regex scans with bounded candidate resolution            | Done   | M      | Med    | §2, §4                                                                                  |
| Done/Shipped: Tests: resolver fallback, preserve_query, open-redirect, loops, SEO import        | Done   | M      | High   | §4                                                                                      |
| Done/Shipped: Add `config/` for status codes, host allowlist, regex/retention                   | Done   | S      | Med    | §4                                                                                      |
| Done/Shipped: Prefix-cascade auto-redirects on page-subtree moves                               | Done   | M      | Med    | §3                                                                                      |
| Done/Shipped: Canonical URL management surface                                                  | Done   | L      | Med    | §3                                                                                      |
| Done/Shipped: Translate Action exception messages                                               | Done   | S      | Low    | §4                                                                                      |