diff --git a/docs/site-map.md b/docs/site-map.md
index 6602ceb5ad..7493bddff4 100644
--- a/docs/site-map.md
+++ b/docs/site-map.md
@@ -1194,9 +1194,13 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir
- `/mockups/development` - Route discovered from app directory Source: `src/app/mockups/development/page.tsx`.
- `/mockups/development/clinical-trust` - Route discovered from app directory Source: `src/app/mockups/development/clinical-trust/page.tsx`.
- `/mockups/development/ledger` - Route discovered from app directory Source: `src/app/mockups/development/ledger/page.tsx`.
+- `/mockups/dictionary-az-filter` - Route discovered from app directory Source: `src/app/mockups/dictionary-az-filter/page.tsx`.
+- `/mockups/dictionary-az-state` - Route discovered from app directory Source: `src/app/mockups/dictionary-az-state/page.tsx`.
- `/mockups/dictionary-browse-header` - Route discovered from app directory Source: `src/app/mockups/dictionary-browse-header/page.tsx`.
- `/mockups/dictionary-browse-header-compact` - Route discovered from app directory Source: `src/app/mockups/dictionary-browse-header-compact/page.tsx`.
- `/mockups/dictionary-control-row` - Route discovered from app directory Source: `src/app/mockups/dictionary-control-row/page.tsx`.
+- `/mockups/dictionary-heading-controls` - Route discovered from app directory Source: `src/app/mockups/dictionary-heading-controls/page.tsx`.
+- `/mockups/dictionary-trailing-toolbar` - Route discovered from app directory Source: `src/app/mockups/dictionary-trailing-toolbar/page.tsx`.
- `/mockups/document-navigation-contract` - Route discovered from app directory Source: `src/app/mockups/document-navigation-contract/page.tsx`.
- `/mockups/document-navigation-final` - Route discovered from app directory Source: `src/app/mockups/document-navigation-final/page.tsx`.
- `/mockups/document-navigation-final-review` - Route discovered from app directory Source: `src/app/mockups/document-navigation-final-review/page.tsx`.
diff --git a/mockups/README.md b/mockups/README.md
index f8316a5f6c..c50b35dff3 100644
--- a/mockups/README.md
+++ b/mockups/README.md
@@ -107,6 +107,66 @@ row in place on the page. Shared mockup chrome is suppressed because each frame
The tab rail reads Terms · Topics · More rather than Search · Browse · More: this round assumes Search and Browse have
merged into one destination, with the site-wide composer as the mode’s only search surface.
+## Dictionary — compact trailing toolbar (2026-08-24)
+
+Runnable study at [`/mockups/dictionary-trailing-toolbar`](../src/app/mockups/dictionary-trailing-toolbar/page.tsx). Two frames.
+
+The original Filter band stays on browse and search (count, optional query, Filter). Compact Terms / Abbreviations and A–Z sit **under that band on the right**. Topics live inside Filter, not on the title row.
+
+Shared mockup chrome is suppressed because each frame draws its own top bar, mode nav and composer.
+
+## Dictionary — browse vs search (recommended split, 2026-08-24)
+
+Runnable study at [`/mockups/dictionary-az-state`](../src/app/mockups/dictionary-az-state/page.tsx). Two frames only.
+
+Filter is the shared `ResultFilterTrigger`. Abbreviations live inside that sheet as Show. A–Z is browse navigation: while the query is empty, A–Z and Filter sit together in the trailing corner of the heading row; as soon as a search runs, A–Z stands down and Filter occupies the original results band. Tap the composer (or the clear X) to feel the handoff.
+
+| State | A–Z | Filter |
+| ------ | ----------------------------------- | --------------------------------------- |
+| Browse | Trailing corner, paired with Filter | Original trigger, same trailing cluster |
+| Search | Hidden | Original trigger, in the results band |
+
+The earlier three-home study at [`/mockups/dictionary-az-filter`](../src/app/mockups/dictionary-az-filter/page.tsx) asked A–Z to live on a search screen. This page is the recommended alternative.
+
+Shared mockup chrome is suppressed because each frame draws its own top bar, mode nav and composer.
+
+## Dictionary — original Filter at the top, three A–Z homes (2026-08-24)
+
+Runnable study at [`/mockups/dictionary-az-filter`](../src/app/mockups/dictionary-az-filter/page.tsx). Three frames only.
+
+The Filter button is the shared `ResultFilterTrigger` used on every search page, sitting in the results band under the title. Abbreviations leave the page and become a Show lens inside that sheet. A–Z is the remaining placement question.
+
+| Direction | A–Z home | Trade-off |
+| --------- | ------------------------------------------ | --------------------------------------------------------------------- |
+| 01 Title | Next to **Clinical terms** | Band stays count + query + Filter, like other modes |
+| 02 Band | Beside Filter in the original results band | Two narrowing controls on the one line that already carries the query |
+| 03 Filter | Inside Filter, under Show | Quietest band; A–Z is one extra tap |
+
+Shared mockup chrome is suppressed because each frame draws its own top bar, mode nav and composer.
+
+## Dictionary — Filter in the bar, Terms and A–Z beside the title (2026-08-23)
+
+Runnable study at [`/mockups/dictionary-heading-controls`](../src/app/mockups/dictionary-heading-controls/page.tsx).
+This is a layout question, not a production change.
+
+Round 1 asked whether Filter can leave the control row. Round 2 moved the query display **under** the Terms / Abbreviations buttons once text is there. Round 3 asks where **A–Z** goes during that search: production and directions 03–06 hide it, because a letter jump does not rank a result set. If it must stay visible, 07–10 try four homes on the 06 query-field chrome.
+
+| Direction | When text is there | Trade-off |
+| --------------------- | ---------------------------------------------------------------- | --------------------------------------------------- |
+| Now | Band above the toggle | Count and query share one truncated line |
+| 01 Persistent bar | Toggle on the heading; Filter always in a slim bar | A bar is present even with no query |
+| 02 Ribbon only | Toggle on the heading; no bar until a search | You cannot facet the idle catalogue |
+| 03 Band under buttons | Title → toggle → production band | Query still truncates against Filter |
+| 04 Query chip | Title → toggle → count + dismissible query chip + Filter | Chip and Filter can wrap at 320 px |
+| 05 Query owns a line | Title → toggle → full query, then count / clear / Filter | Extra row, but both words of the query stay visible |
+| 06 Query field | Title → toggle → query field + Filter; count lives on the toggle | Field is a second composer echo |
+| 07 Beside the toggle | A–Z stays on the button row during search | Competes with Abbreviations on a 390 px row |
+| 08 In the query row | A–Z sits with Filter beside the query field | The field is narrower; query may truncate |
+| 09 Next to the title | A–Z is a heading companion | Title row wraps on 320 px |
+| 10 Inside Filter | No page chip; letter jump lives in the Filter sheet | A–Z is one extra tap, and not visible while reading |
+
+Shared mockup chrome is suppressed because each frame draws its own top bar, mode nav and composer.
+
## Phone Choose mode sheet YES comps
Runnable study at [`/mockups/phone-mode-sheet-yes`](../src/app/mockups/phone-mode-sheet-yes/page.tsx): design review of the shipping phone mode sheet plus **YES 01 perfected** (sectioned clinical list — shipping recommendation) and YES 02 (icon deck alternate). Shared mockup chrome is suppressed so only the in-frame sheet is judged.
diff --git a/src/app/mockups/dictionary-az-filter/page.tsx b/src/app/mockups/dictionary-az-filter/page.tsx
new file mode 100644
index 0000000000..c1f261f0e1
--- /dev/null
+++ b/src/app/mockups/dictionary-az-filter/page.tsx
@@ -0,0 +1,5 @@
+import { DictionaryAzFilterMockupsPage } from "@/components/dictionary-az-filter-mockups";
+
+export default function DictionaryAzFilterMockupRoute() {
+ return ;
+}
diff --git a/src/app/mockups/dictionary-az-state/page.tsx b/src/app/mockups/dictionary-az-state/page.tsx
new file mode 100644
index 0000000000..1bf607830f
--- /dev/null
+++ b/src/app/mockups/dictionary-az-state/page.tsx
@@ -0,0 +1,5 @@
+import { DictionaryAzStateMockupsPage } from "@/components/dictionary-az-state-mockups";
+
+export default function DictionaryAzStateMockupRoute() {
+ return ;
+}
diff --git a/src/app/mockups/dictionary-heading-controls/page.tsx b/src/app/mockups/dictionary-heading-controls/page.tsx
new file mode 100644
index 0000000000..a2f5d4c06f
--- /dev/null
+++ b/src/app/mockups/dictionary-heading-controls/page.tsx
@@ -0,0 +1,5 @@
+import { DictionaryHeadingControlsMockupsPage } from "@/components/dictionary-heading-controls-mockups";
+
+export default function DictionaryHeadingControlsMockupRoute() {
+ return ;
+}
diff --git a/src/app/mockups/dictionary-trailing-toolbar/page.tsx b/src/app/mockups/dictionary-trailing-toolbar/page.tsx
new file mode 100644
index 0000000000..df0516559f
--- /dev/null
+++ b/src/app/mockups/dictionary-trailing-toolbar/page.tsx
@@ -0,0 +1,5 @@
+import { DictionaryTrailingToolbarMockupsPage } from "@/components/dictionary-trailing-toolbar-mockups";
+
+export default function DictionaryTrailingToolbarMockupRoute() {
+ return ;
+}
diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx
index 010a9f93fe..d2f49e2097 100644
--- a/src/app/mockups/mockups-layout-client.tsx
+++ b/src/app/mockups/mockups-layout-client.tsx
@@ -24,13 +24,10 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
const isStandaloneDocumentFlow = pathname === "/mockups/document-search";
const isUniversalSearchRedesignMockup = pathname === "/mockups/universal-search-redesign";
const isSearchHeadingMockup = pathname === "/mockups/search-heading";
- // Draws its own phone/desktop frames with a top bar, mode nav and composer in
- // every frame, because the header under study sits directly beneath them.
- const isDictionaryBrowseHeaderMockup = pathname.startsWith("/mockups/dictionary-browse-header");
- // Same reason: this study draws its own tab rail, page card and site-wide
- // composer inside every frame, so shared chrome would read as a second real
- // header and a second real search bar over the row under review.
- const isDictionaryControlRowMockup = pathname === "/mockups/dictionary-control-row";
+ // Dictionary chrome studies draw their own top bar, mode nav, page card and
+ // composer inside every frame, so shared mockup chrome would read as a second
+ // real header and a second real search bar over the row under review.
+ const isDictionaryChromeStudyMockup = pathname.startsWith("/mockups/dictionary-");
// Renders the results header inside its own device frames; the shared composer
// would read as a second, real search bar over the study.
const isSearchRefineAdaptiveMockup = pathname === "/mockups/search-refine-adaptive";
@@ -139,8 +136,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
!isSidebarLiveMockup &&
!isCaringContactMockup &&
!isCarePlanMockup &&
- !isDictionaryBrowseHeaderMockup &&
- !isDictionaryControlRowMockup
+ !isDictionaryChromeStudyMockup
}
chromeVisible={
!isSourceOverlayRedesignMockup &&
@@ -165,8 +161,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
!isSidebarLiveMockup &&
!isCaringContactMockup &&
!isCarePlanMockup &&
- !isDictionaryBrowseHeaderMockup &&
- !isDictionaryControlRowMockup
+ !isDictionaryChromeStudyMockup
}
>
{children}
diff --git a/src/components/dictionary-az-filter-mockups.tsx b/src/components/dictionary-az-filter-mockups.tsx
new file mode 100644
index 0000000000..16a6332b36
--- /dev/null
+++ b/src/components/dictionary-az-filter-mockups.tsx
@@ -0,0 +1,438 @@
+"use client";
+
+import { useId, useState } from "react";
+import { ChevronDown, Menu, MessageSquarePlus, Plus, Search, SendHorizontal, X } from "lucide-react";
+
+import { ResultFilterTrigger } from "@/components/clinical-dashboard/result-filter-control";
+import { focusRing, letters, ResultRow, type SampleEntry } from "@/components/dictionary-browse-header-mockups";
+import { cn } from "@/components/ui-primitives";
+
+/* ------------------------------------------------------------------ *
+ * Dictionary → original Filter at the top, A–Z homes (2026-08-24)
+ *
+ * Three frames only. Shared decisions:
+ * - The Filter control is the real ResultFilterTrigger used on every
+ * search page, sitting in the results band at the top (under the title).
+ * - Abbreviations leave the page. They are a Show lens inside Filter,
+ * because Terms / Abbreviations partition the list (one is active).
+ * - A–Z is the open question. Each frame tries one home.
+ *
+ * 01 Title A–Z sits next to Clinical terms.
+ * 02 Band A–Z sits beside Filter in the original results band.
+ * 03 Filter A–Z joins Abbreviations inside the Filter sheet.
+ * ------------------------------------------------------------------ */
+
+const QUERY = "mental state examination";
+const TERM_COUNT = 2;
+const ABBR_COUNT = 2;
+const emptyLetters = new Set(["J", "K", "Q", "U", "V", "W", "X", "Y", "Z"]);
+
+type AzHome = "title" | "band" | "sheet";
+type Scope = "terms" | "abbreviations";
+
+const queryEntries: readonly SampleEntry[] = [
+ {
+ term: "Mental state examination",
+ kind: "Clinical finding",
+ alias: "MSE",
+ definition: "A structured assessment of appearance, behaviour, speech, mood, thought, perception and cognition.",
+ },
+ {
+ term: "Mini-Mental State Examination",
+ kind: "Scale",
+ alias: "MMSE",
+ definition: "A brief 30-point screen of orientation, registration, attention, recall and language.",
+ },
+];
+
+function PhoneFrame({
+ azHome,
+ label,
+ note,
+ startSheetOpen = false,
+}: {
+ azHome: AzHome;
+ label: string;
+ note: string;
+ startSheetOpen?: boolean;
+}) {
+ const panelId = useId();
+ const [scope, setScope] = useState("terms");
+ const [letter, setLetter] = useState("All");
+ const [query, setQuery] = useState(QUERY);
+ const [sheet, setSheet] = useState<"none" | "filter" | "letter">(startSheetOpen ? "filter" : "none");
+ const searching = query.length > 0;
+ const count = scope === "abbreviations" ? ABBR_COUNT : TERM_COUNT;
+ const noun = scope === "abbreviations" ? "abbreviations" : "terms";
+ const activeCount = (scope === "abbreviations" ? 1 : 0) + (letter === "All" ? 0 : 1);
+ const filterOpen = sheet === "filter";
+ const letterOpen = sheet === "letter";
+
+ const letterChip = (
+
+ );
+
+ return (
+
+ Filter at the top. Abbreviations in the sheet. Three homes for A–Z.
+
+
+ The Filter button is the same control every search page uses, in the results band under the title. There is
+ no Terms / Abbreviations toggle on the page — that choice is a Show lens inside Filter. Tap Filter on 01 or
+ 02 to see it. Frame 03 starts with the sheet open so A–Z in Filter is visible.
+
+
+
+
+
+
+ Three directions
+
+
+ Same band, same Filter, same query. Only A–Z moves.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/dictionary-az-state-mockups.tsx b/src/components/dictionary-az-state-mockups.tsx
new file mode 100644
index 0000000000..a75fce1fe4
--- /dev/null
+++ b/src/components/dictionary-az-state-mockups.tsx
@@ -0,0 +1,469 @@
+"use client";
+
+import { useId, useState, type ReactNode } from "react";
+import { ChevronDown, Menu, MessageSquarePlus, Plus, Search, SendHorizontal, X } from "lucide-react";
+
+import { ResultFilterTrigger } from "@/components/clinical-dashboard/result-filter-control";
+import {
+ focusRing,
+ letters,
+ ResultRow,
+ sampleEntries,
+ type SampleEntry,
+} from "@/components/dictionary-browse-header-mockups";
+import { cn } from "@/components/ui-primitives";
+
+/* ------------------------------------------------------------------ *
+ * Dictionary → browse vs search (recommended split, 2026-08-24)
+ *
+ * Two frames only. Same Filter, same Abbreviations-in-Filter rule.
+ * A–Z is browse navigation, so it stands down the moment a query runs.
+ * On browse, A–Z and Filter sit together in the trailing corner so the title
+ * stays a title and the two list controls read as one toolbar.
+ * ------------------------------------------------------------------ */
+
+const QUERY = "mental state examination";
+const TERM_COUNT_BROWSE = sampleEntries.length;
+const TERM_COUNT_SEARCH = 2;
+const ABBR_COUNT = 2;
+const emptyLetters = new Set(["J", "K", "Q", "U", "V", "W", "X", "Y", "Z"]);
+
+type Scope = "terms" | "abbreviations";
+type Sort = "relevance" | "az" | "za";
+type Sheet = "none" | "filter" | "letter";
+
+const queryEntries: readonly SampleEntry[] = [
+ {
+ term: "Mental state examination",
+ kind: "Clinical finding",
+ alias: "MSE",
+ definition: "A structured assessment of appearance, behaviour, speech, mood, thought, perception and cognition.",
+ },
+ {
+ term: "Mini-Mental State Examination",
+ kind: "Scale",
+ alias: "MMSE",
+ definition: "A brief 30-point screen of orientation, registration, attention, recall and language.",
+ },
+];
+
+function PhoneChrome({ children }: { children: ReactNode }) {
+ return (
+ <>
+
+ {searching
+ ? "Original Filter in the band. No A–Z. Abbreviations live in Show."
+ : "A–Z and Filter share the trailing corner. No results band yet."}
+
+ Two phones, one rule. Abbreviations live inside the original Filter sheet as Show. While browsing, A–Z and
+ Filter sit together in the trailing corner so the title stays a title. The moment a query runs, A–Z stands
+ down and Filter moves into the results band — the same control every other search page uses. Tap the
+ composer (or the clear X) to feel the handoff.
+
+
+
+
+
+
+ Two states
+
+
+ Left starts as browse. Right starts as search. Same component, different starting query.
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/dictionary-heading-controls-mockups.tsx b/src/components/dictionary-heading-controls-mockups.tsx
new file mode 100644
index 0000000000..8ae074901b
--- /dev/null
+++ b/src/components/dictionary-heading-controls-mockups.tsx
@@ -0,0 +1,870 @@
+"use client";
+
+import { useState } from "react";
+import { ChevronDown, Funnel, Menu, MessageSquarePlus, Plus, Search, SendHorizontal, X } from "lucide-react";
+
+import { focusRing, letters, ResultRow, type SampleEntry } from "@/components/dictionary-browse-header-mockups";
+import { cn } from "@/components/ui-primitives";
+
+/* ------------------------------------------------------------------ *
+ * Dictionary → heading companions, round 2 (2026-08-24)
+ *
+ * Round 1 asked whether Filter can leave the control row. Round 2 starts
+ * from the live search state: the query band currently sits *above* the
+ * Terms / Abbreviations toggle, so a two-word query and Filter fight for
+ * one line while the buttons sit underneath looking orphaned.
+ *
+ * Image two (browse) already has the stack to keep:
+ * title → toggle + A–Z → results
+ * These directions put the query display bar *under* those buttons when
+ * text is there, and try four ways to stop the count and the typed words
+ * jamming into one truncated phrase.
+ *
+ * Tap the composer in any frame to run “mental state examination” and
+ * clear it again.
+ *
+ * A–Z currently stands down during a search — the live catalogue does the
+ * same, because a letter jump is meaningless against a ranked result set.
+ * Round 3 asks where it should live if it must stay visible once text is
+ * there: beside the toggle, in the query row, next to the title, or inside
+ * Filter.
+ * ------------------------------------------------------------------ */
+
+const TERM_COUNT = 96;
+const ABBR_COUNT = 11;
+const QUERY = "mental state examination";
+const QUERY_TERMS = 2;
+const QUERY_ABBRS = 2;
+
+type BrowseLayout = "now" | "persistent" | "ribbon";
+type SearchLayout = "under-band" | "under-chip" | "under-own-line" | "under-field";
+type Layout = BrowseLayout | SearchLayout;
+type LetterHome = "hide" | "buttons" | "bar" | "heading" | "sheet";
+
+function usePageState({
+ startSearching = false,
+ empty = false,
+ startSheet = "none",
+ letterHome = "hide",
+}: {
+ startSearching?: boolean;
+ empty?: boolean;
+ startSheet?: "none" | "letter" | "filter";
+ letterHome?: LetterHome;
+} = {}) {
+ const [scope, setScope] = useState<"terms" | "abbr">("terms");
+ const [letter, setLetter] = useState("All");
+ const [query, setQuery] = useState(startSearching || empty ? QUERY : "");
+ const [sheet, setSheet] = useState<"none" | "letter" | "filter">(startSheet);
+ const searching = query.length > 0;
+ const termCount = searching ? (empty ? 0 : QUERY_TERMS) : TERM_COUNT;
+ const abbrCount = searching ? (empty ? 0 : QUERY_ABBRS) : ABBR_COUNT;
+ const count = scope === "abbr" ? abbrCount : termCount;
+ const noun =
+ scope === "abbr"
+ ? Number(count) === 1
+ ? "abbreviation"
+ : "abbreviations"
+ : Number(count) === 1
+ ? "term"
+ : "terms";
+ return {
+ scope,
+ setScope,
+ letter,
+ setLetter,
+ query,
+ setQuery,
+ sheet,
+ setSheet,
+ searching,
+ empty,
+ termCount,
+ abbrCount,
+ count,
+ noun,
+ letterHome,
+ };
+}
+
+type PageState = ReturnType;
+
+const emptyLetters = new Set(["J", "K", "Q", "U", "V", "W", "X", "Y", "Z"]);
+
+const catalogueEntries: readonly SampleEntry[] = [
+ {
+ term: "Acceptance and commitment therapy",
+ kind: "Therapy",
+ alias: "ACT",
+ definition: "A therapy that develops acceptance, present-moment awareness and action guided by personal values.",
+ },
+ {
+ term: "Acute dystonia",
+ kind: "Clinical finding",
+ definition:
+ "A sudden medication-associated sustained muscle contraction that produces abnormal posture or movement.",
+ },
+ {
+ term: "Advance statement",
+ kind: "Legal / ethical",
+ definition:
+ "A written record of a person's treatment preferences made while they have capacity, for use in later care.",
+ },
+];
+
+const queryEntries: readonly SampleEntry[] = [
+ {
+ term: "Mental state examination",
+ kind: "Clinical finding",
+ alias: "MSE",
+ definition: "A structured assessment of appearance, behaviour, speech, mood, thought, perception and cognition.",
+ },
+ {
+ term: "Mini-Mental State Examination",
+ kind: "Scale",
+ alias: "MMSE",
+ definition: "A brief 30-point screen of orientation, registration, attention, recall and language.",
+ },
+];
+
+function ScopeToggle({ state }: { state: PageState }) {
+ const options = [
+ { value: "terms" as const, label: "Terms", count: state.termCount },
+ { value: "abbr" as const, label: "Abbreviations", count: state.abbrCount },
+ ];
+ return (
+
+ );
+}
+
+/* Image two’s button row: title stays alone, toggle + A–Z sit on the next
+ line. Filter only joins this row while browsing — once there is text, it
+ moves into the display bar underneath. */
+function ButtonsThenBarRow({
+ state,
+ showFilter,
+ showLetter,
+}: {
+ state: PageState;
+ showFilter: boolean;
+ showLetter: boolean;
+}) {
+ return (
+
+ {state.letterHome === "sheet"
+ ? "A–Z lives in this sheet during a search, next to topics, kinds and sources."
+ : "Topics, kinds and sources stay in this sheet. Scope and letter stay on the page so the sheet does not hide the view you are already in."}
+
+ Browse already works as title, then Terms / A–Z, then results. The live search state inverts that: the query
+ band sits above the toggle, so “mental state examination” and Filter share one jammed line. These frames
+ keep the button row where it is and put the display bar underneath once there is text. A–Z currently
+ disappears during a search — the same as production. The section below tries four homes for it. Tap any
+ composer to clear or restore the query.
+
+
+
+
+
+
+ When text is there
+
+
+ Same stack as the browse frame: title, then the joined toggle, then the query display. Four ways to stop the
+ count and the typed words colliding.
+
+
+
+
+
+
+
+
+
+
+
+ Where A–Z goes
+
+
+ Today it stands down the moment a query runs, because a letter jump does not rank a result set. If it must
+ stay on screen, it has to sit somewhere that does not fight the query field. All four frames use direction 06
+ as the search chrome.
+
+
+
+
+
+
+
+
+
+
+
+ Zero matches
+
+
+ The jammed live state. Count is zero, so it must not sit inside the query field. Direction 06 keeps “0” on the
+ toggle and the typed words in the field.
+
+
+
+
+
+
+
+
+
+ Browse, for comparison
+
+
+ Round 1 frames. Image two is 02: title, then toggle and A–Z, no bar until a search.
+
+
+
+
+
+
+
+
+
+
+ 320 px · query under the buttons
+
+
+ Direction 05 at the stress width. The query keeps both words; Filter stays on the count row.
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/dictionary-trailing-toolbar-mockups.tsx b/src/components/dictionary-trailing-toolbar-mockups.tsx
new file mode 100644
index 0000000000..8ef87657af
--- /dev/null
+++ b/src/components/dictionary-trailing-toolbar-mockups.tsx
@@ -0,0 +1,449 @@
+"use client";
+
+import { useId, useState, type ReactNode } from "react";
+import { ChevronDown, Menu, MessageSquarePlus, Plus, Search, SendHorizontal, X } from "lucide-react";
+
+import { ResultFilterTrigger } from "@/components/clinical-dashboard/result-filter-control";
+import {
+ focusRing,
+ letters,
+ ResultRow,
+ sampleEntries,
+ type SampleEntry,
+} from "@/components/dictionary-browse-header-mockups";
+import { cn } from "@/components/ui-primitives";
+
+/* ------------------------------------------------------------------ *
+ * Dictionary → compact trailing toolbar (2026-08-24)
+ *
+ * Title stays a title. The original Filter search-band stays on browse and
+ * search. Compact Terms / Abbreviations and A–Z sit under that band on the
+ * right. Topics live inside Filter.
+ * ------------------------------------------------------------------ */
+
+const QUERY = "mental state examination";
+const TERM_COUNT_BROWSE = sampleEntries.length;
+const TERM_COUNT_SEARCH = 2;
+const ABBR_COUNT = 2;
+const emptyLetters = new Set(["J", "K", "Q", "U", "V", "W", "X", "Y", "Z"]);
+
+const topicOptions = [
+ { id: "all", label: "All topics" },
+ { id: "assessment", label: "Assessment" },
+ { id: "therapies", label: "Therapies" },
+ { id: "findings", label: "Clinical findings" },
+] as const;
+
+type Scope = "terms" | "abbreviations";
+type Sheet = "none" | "letter" | "filter";
+
+const queryEntries: readonly SampleEntry[] = [
+ {
+ term: "Mental state examination",
+ kind: "Clinical finding",
+ alias: "MSE",
+ definition: "A structured assessment of appearance, behaviour, speech, mood, thought, perception and cognition.",
+ },
+ {
+ term: "Mini-Mental State Examination",
+ kind: "Scale",
+ alias: "MMSE",
+ definition: "A brief 30-point screen of orientation, registration, attention, recall and language.",
+ },
+];
+
+const chip =
+ "inline-flex h-7 max-h-7 shrink-0 items-center gap-0.5 overflow-hidden rounded-md border border-[color:var(--border)] bg-[color:var(--surface)] px-1.5 text-2xs font-semibold leading-none text-[color:var(--clinical-accent)]";
+
+function PhoneChrome({ children }: { children: ReactNode }) {
+ return (
+ <>
+
+ {searching
+ ? "The original Filter band stays. Query and clear sit in it. Compact Terms / Abbr. and A–Z sit under the band."
+ : "The original Filter band stays even without a query. Compact Terms / Abbr. and A–Z sit under the band."}
+
+ Keep the Filter band. Put Terms, Abbreviations, and A–Z under it.
+
+
+ The original Filter band stays on browse and search — count, optional query, and Filter. Compact Terms /
+ Abbreviations and A–Z sit underneath on the right. Topics live inside Filter. Tap the composer to feel the
+ handoff.
+
+
+
+
+
+
+ Two states
+
+
+ Left starts as browse. Right starts as search. Filter is always in the band. Compact Terms / Abbreviations and
+ A–Z sit under it. Topics live inside Filter.
+