diff --git a/docs/tables_frontend_overhaul_implementation_plan.md b/docs/tables_frontend_overhaul_implementation_plan.md
index 54ababd..b555e18 100644
--- a/docs/tables_frontend_overhaul_implementation_plan.md
+++ b/docs/tables_frontend_overhaul_implementation_plan.md
@@ -63,6 +63,7 @@ It is intentionally implementation-focused:
| 2026-03-21 | Post-P2 fix 3 | Completed | Moved omnibox overlay ownership from the header subtree into `AppShell` itself via a shared omnibox state service and a top-level palette host, which restored full-screen backdrop coverage and reliable outside-click close behavior. |
| 2026-03-21 | P3.1 | Completed | Split `Tables.razor` into focused table components for the selector header, context bar, canvas, and legend while leaving loading, deep-link synchronization, and dialog state in the page host. |
| 2026-03-21 | P3.2 | Completed | Replaced the floating table picker with a permanent left-rail layout, converted the old selector component into a real page header, and kept the current selection flow intact inside the new reference frame. |
+| 2026-03-21 | P3.3 | Completed | Added rail search, family filters, pinned and recent sections, curated status chips, and keyboard up/down plus Enter handling on top of the new permanent table index rail. |
### Lessons Learned
@@ -95,6 +96,7 @@ It is intentionally implementation-focused:
- Backdrop and outside-click behavior depend on overlay ownership as much as CSS. If the trigger owns the overlay inside a sticky header subtree, fixed-position assumptions can break; shell-level overlays should be rendered by the shell, not by individual header controls.
- The `Tables` rewrite is safer when orchestration and rendering are separated early. Keeping loading, persistence, and dialog state in the page host while extracting render-only components makes later layout and interaction changes much lower risk.
- The `Tables` navigation model needs its own persistent geometry before advanced behaviors land. Converting the selector to a real rail first keeps later search and keyboard work from being tangled up with another structural rewrite.
+- Rail keyboard behavior is easiest to maintain when it works from one deduplicated option order, even if the UI shows multiple sections. Keeping one internal option list avoids separate arrow-key state per section.
## Target Outcomes
@@ -457,7 +459,7 @@ Build the shared interaction infrastructure needed by multiple destinations befo
| --- | --- | --- |
| `P3.1` | Completed | `Tables.razor` now acts as the stateful host while selector/header/canvas/legend rendering lives in dedicated `Components/Tables` components. |
| `P3.2` | Completed | The old dropdown picker is gone; `/tables` now uses a permanent left rail and a real page header while keeping the current selection flow intact. |
-| `P3.3` | Pending | Add search, keyboard navigation, pinned/recent sections, curated percentage chips, and family filtering to the rail. |
+| `P3.3` | Completed | The rail now supports search-as-you-type, family filters, pinned and recent sections, curated status chips, and a deduplicated arrow/Enter keyboard path. |
| `P3.4` | Pending | Introduce the sticky context bar with roll jump and mode/filter controls. |
| `P3.5` | Pending | Rework the canvas for sticky headers, sticky roll bands, stronger reading emphasis, and density control. |
| `P3.6` | Pending | Remove visible resting-state action stacks from non-selected cells. |
diff --git a/src/RolemasterDb.App/Components/Pages/Tables.razor b/src/RolemasterDb.App/Components/Pages/Tables.razor
index 9047abe..b1be804 100644
--- a/src/RolemasterDb.App/Components/Pages/Tables.razor
+++ b/src/RolemasterDb.App/Components/Pages/Tables.razor
@@ -28,6 +28,8 @@
diff --git a/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor b/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor
index d39093b..dcb3242 100644
--- a/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor
+++ b/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor
@@ -4,46 +4,319 @@
Choose a table, then read from the roll band across to the result you need.
-
- @foreach (var table in Tables)
+
+
+
+
+ @if (familyFilters.Count > 1)
{
-
+
+ @foreach (var family in familyFilters)
+ {
+ var isAllFilter = string.IsNullOrEmpty(family);
+ var label = isAllFilter ? "All families" : family;
+
+
+ }
+