/** * THE INBOX / CONVERSATION SPLIT - one definition, four screens. * * THE DEFECT THIS EXISTS FOR, raised by Spencer looking at the screen 2026-08-17: * *"the left column of the messages view is too wide and dominating too much screen space compared to the * actual message interface."* Measured on a 1829 px monitor: the inbox took **468 px - 33% of the app** - * and the widest text painted inside it was **86 px**. About **81% of that column was empty.** * * **The cause was not a badly-chosen ratio. It was a ratio that never changes.** All four screens used * `grid-cols-6` with `md:col-span-2` and `lg:col-span-2` - **identical spans**, so the split is frozen from * tablet upward and the column takes a THIRD of whatever it is given. On a 2560 px monitor that is ~850 px * to paint the same 86 px of text. * * **AND IT WAS COPIED FOUR TIMES.** The card named one file; the same block sits in * `MessagesPage`, and in the staff panels for Patients, Colleagues and Collaboration teams. The screen * Spencer measured was the **staff Patients** panel - *not* the file the finding cited. Fixing the cited * one alone would have left him looking at an unchanged screen. **One constant now, so the next change is * one change.** * * ## The shape, and why each half is what it is * `minmax(16rem, 22rem)` - a **floor** and a **ceiling** rather than a fraction. * - **16rem (256 px) floor** keeps the narrow end intact: at `md` the old 2-of-6 split gave ~253 px, so a * tablet reads as it did. That is this change's control - a clinician on a 1024 tablet is a stated * target, and a preview that truncates to nothing there would make the row worse, not better. * - **22rem (352 px) ceiling** is what stops the growth. A one-line preview **earns** width; it does not * earn unbounded width, and every pixel past the ceiling was going to the conversation anyway. * - `1fr` gives the rest to the thread pane, so the conversation grows with the monitor instead of the * navigation list. * * Below `md` this is a SINGLE column and the panes swap by `hidden`/`block` - the mobile behaviour is * unchanged, and it is why the flags below take the same boolean the pages already track. */ // The two columns are separate cards and need a gutter to read as two things rather than one split // down the middle. Only from `md`, where the split exists at all - below it they are one column and a // gap would be a stripe under whichever pane is showing. const COLUMNS = 'grid grid-cols-1 md:grid-cols-[minmax(16rem,22rem)_1fr] md:gap-3'; /** * THE SPLIT FILLS THE BOX IT IS GIVEN, AND THE PAGE SAYS HOW TALL THAT BOX IS. * * It used to claim the viewport itself - `h-[calc(100vh-100px)]` - which is the right answer only where * the split IS the page. Under the staff Messages shell it is not: a title, the patient messaging * toggle and a tab row sit above it inside a box of that same height, so the split asked for the whole * box and overflowed it by exactly the height of that chrome. * * MEASURED: 139px over, at 900 AND at 1100 viewport height. A constant overflow is what says the cause * is fixed content with nowhere to go rather than a short screen. The container is `overflow-hidden`, so * there was no scrollbar - and opening a thread scrolled it 122.86px to bring the selected row into * view, taking the heading and the tab switch off the top with no gesture that brings them back. * * `min-h-0` is load-bearing rather than decoration: a flex child's default `min-height: auto` refuses to * shrink below its content, so without it a pane grows the page instead of scrolling inside itself, * which is this same defect one level out. * * The chrome is not a fixed number and must not become one. `PatientMessagingToggle` renders nothing for * a role whose patients cannot message them individually, so the block above the split is a different * height for a receptionist than for a practitioner. Filling the parent is right for every one of them; * a second measured constant would be right only for whichever was measured. */ export const MESSAGES_SPLIT_GRID = `${COLUMNS} h-full min-h-0 overflow-hidden`; /** * The page that holds a Messages split. One height, stated once per screen. * * Spencer, 2026-09-10: *"we need to ensure that the messaging input and buttons are always fixed * to the bottom of the viewport and not hidden below the fold or hidden entirely like they are now."* * * IT USED TO BE `100vh - 100px`, AND 100px WAS NEVER THIS PORTAL'S CHROME. The patient portal puts * a practice header, an action row, a consent banner and a breadcrumb above this page - about 265px on * a signed-in patient, and a different number the moment the banner is dismissed. The page therefore * asked for more height than it had, the overflow went off the bottom, and the composer - the last * thing in the column - was the part that fell off. The thread scrolled; the box it scrolled inside * did not fit. * * The comment two blocks up already said the chrome *"is not a fixed number and must not become * one"*, about the staff shell. It was true here too and this constant was the counter-example. * * SO THE PAGE MEASURES ITS OWN TOP and takes what is actually left. `--ak-messages-top` is written * by `useMessagesViewportFit` from the element's own position, so a dismissed banner, a wrapped header * or a second breadcrumb line all correct themselves. The fallback keeps the old behaviour for the * one frame before the measurement lands rather than collapsing to nothing. * * `dvh` and not `vh`: on a phone `vh` is the tallest the viewport ever gets, so the browser's own * chrome hides exactly the strip this rule exists to protect. */ export const MESSAGES_PAGE_HEIGHT = 'flex flex-col overflow-hidden h-[calc(100dvh-var(--ak-messages-top,100px))]'; /** * A box between the page and the split - the Card, and its content - which takes what is left after the * chrome above it and passes it down instead of adding a height of its own. */ export const MESSAGES_PAGE_BODY = 'flex-1 min-h-0 flex flex-col'; /** * The inbox / navigation column. Hidden on mobile once a conversation is open. * * IT BOUNDS THE CARD; IT DOES NOT SCROLL. This used to carry `overflow-y-auto`, which put the * scrollbar on the grid CELL - outside the card's own border, in the gutter between the list and the * conversation. The bar then belonged to neither panel and read as page furniture wedged between two * cards. A list scrolls inside the card that contains it, so the overflow moved in with it. */ export const messagesInboxPane = (showConversationMobile: boolean): string => `${showConversationMobile ? 'hidden' : 'block'} md:block overflow-hidden min-h-0`; /** * The conversation column. * * `min-w-0` is load-bearing rather than tidy: a `1fr` grid track defaults to `min-width: auto`, so a * wide message or a long unbroken string inside it can push the track past its share and squeeze the * inbox - the opposite of the defect, and just as invisible until it happens. * * IT IS A FLEX COLUMN SO ITS CHILDREN SHARE THE HEIGHT, and that is the whole reason for the display. * This used to be a plain `block` box, and the `Chat` card inside it carries `h-full` - so with a * second child above it (the front desk's claim strip) the two asked for 757px inside 709px. The box * is `overflow-hidden`, so there was no scrollbar to give it back: the column was simply scrolled 47px * - exactly the strip's own height - and the strip, with `I'll take this` in it, was painted nowhere. * Measured on the front desk: the button reported `visible`, `enabled`, opacity 1, and * `document.elementFromPoint` at its own centre returned the tab row above the pane. * * As a flex column the card shrinks to what is left instead of claiming the lot. The card is a scroll * container, so its automatic minimum size is already 0 and it can shrink; `min-h-0` here is for the * pane itself, which is a grid track and would otherwise floor at its content. * * The mobile swap stays a display utility, which is why `flex` replaces `block` in BOTH arms rather * than being added beside it - two display utilities in one class string are resolved by source order * in the generated sheet, not by the order they are written. */ export const messagesThreadPane = (showConversationMobile: boolean): string => `${showConversationMobile ? 'flex' : 'hidden'} md:flex flex-col min-h-0 overflow-hidden min-w-0`;