/* 07-documents - part of the former static/style.css.
*
* The stylesheet is split by area, but the split is mechanical: rules were
* moved verbatim and NOT regrouped. The numeric prefix is the load order, and
* that order is what preserves the cascade. A rule sits in the file its
* original position fell into, so an area's rules are not all in its file and
* a file is not only that area's rules.
*
* Do not reorder the tags in static/index.html, and do not move a rule
* between files: with 1,151 redeclarations and 1,886 !important in this
* cascade, either can change which declaration wins.
*
* static/index.html and the PRECACHE list in static/sw.js must agree on this
* order. tests/test_static_stylesheet_manifest.py and
* tests/test_css_computed_style_snapshot.py fail if that drifts.
*/
.modal.modal-left-docked {
align-items: stretch;
justify-content: flex-start;
}
.modal.modal-left-docked .modal-content {
border-right: 1px solid var(--border);
box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18);
border-radius: 0;
/* Pin transitions OFF on the dock's positioning properties. The wide
sidebar collapse/expand changes --sidebar-w instantly, which means
the docked modal's `left: calc(...)` jumps by ~240px. Any CSS
transition on `left` (inherited or otherwise) would animate that
jump as a fly-across. Same defense for right-docked / fullscreen
panels in case future themes add a generic transition. */
transition: none !important;
}
.modal.modal-right-docked .modal-content,
#email-lib-modal.email-lib-fullscreen .modal-content,
#doclib-modal.doclib-fullscreen .doclib-modal-content {
transition: none !important;
}
.modal.modal-right-docked .email-reader-header,
.modal.modal-left-docked .email-reader-header {
flex-direction: column;
/* Stretch the meta to the full header width so the absolute
action cluster's right:0 lands at the right edge (was sizing
meta to chip content width and centering it). */
align-items: stretch;
gap: 6px;
}
/* Docked panes: 4px between From→To and 4px between To→Cc.
Meta has its own 2px gap (From→details) so an extra 2px on
details margin-top gives a 4px From→To distance, and gap:4 on
the details flex column gives 4px between To and Cc. */
.modal.modal-right-docked .email-reader-meta-details,
.modal.modal-left-docked .email-reader-meta-details {
margin-top: 2px;
gap: 4px;
}
/* Docked: always show To/Cc — there's vertical room reserved for
the action cluster already and the chevron toggle just adds
friction. Force the hidden attribute open and hide the chevron. */
.modal.modal-right-docked .email-reader-meta-details[hidden],
.modal.modal-left-docked .email-reader-meta-details[hidden] {
display: flex !important;
}
.modal.modal-right-docked .email-reader-meta-toggle,
.modal.modal-left-docked .email-reader-meta-toggle {
display: none;
}
.modal.modal-right-docked .email-reader-header > .email-reader-actions,
.modal.modal-left-docked .email-reader-header > .email-reader-actions {
position: static;
align-self: flex-end;
background: none;
padding-left: 0;
}
/* Docked modal: keep all meta rows as flex so From / To / Cc
labels align at the same x. Chips already overflow-scroll
inside each row when there are too many. No per-row margin-top
here — the spacing between rows comes entirely from
.email-reader-meta-details gap so From→To and To→Cc match. */
.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from),
.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) {
padding-right: 0;
}
.modal.modal-right-docked .email-reader-meta-row strong,
.modal.modal-left-docked .email-reader-meta-row strong {
min-width: 0;
}
.modal.modal-right-docked .recipient-chip,
.modal.modal-left-docked .recipient-chip {
max-width: 100%;
}
.archive-list {
margin-top: 8px;
border-top: 1px solid var(--border);
padding-top: 8px;
}
#archive-modal .memory-sort-select,
#archive-modal .memory-toolbar-btn {
height: 32px;
}
.archive-menu-btn {
position: relative;
top: 2px;
}
#archive-chips .doclib-chip {
height: 22px;
display: inline-flex;
align-items: center;
font-size: 10px;
padding: 0 8px;
}
/* Library tab panels — Chats / Archive / Research / Documents share the
same toolbar dimensions so the sort dropdown + Select button line up
identically across tabs. */
#doclib-panel-chats .memory-sort-select,
#doclib-panel-archive .memory-sort-select,
#doclib-panel-research .memory-sort-select,
[data-doclib-panel="documents"] .memory-sort-select {
height: 24px;
font-size: 11px;
width: 86px;
}
#doclib-panel-chats .memory-toolbar-btn,
#doclib-panel-archive .memory-toolbar-btn,
#doclib-panel-research .memory-toolbar-btn,
[data-doclib-panel="documents"] .memory-toolbar-btn {
height: 24px;
font-size: 11px;
position: relative;
top: -3px;
}
/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */
#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; }
#doclib-panel-research .memory-toolbar-btn { top: -4.5px; }
.library-sort-picker {
width: 108px;
flex: 0 0 108px;
top: 1px;
}
.library-sort-picker .library-sort-btn {
top: 0;
width: 100%;
}
.library-sort-picker .library-sort-menu {
top: calc(100% + 4px);
width: max(100%, 154px);
}
.library-sort-picker .library-sort-item[aria-selected="true"] {
background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent);
}
#doclib-panel-chats #doclib-chats-tidy-btn,
#doclib-panel-chats #doclib-chats-select-btn,
#doclib-panel-archive #doclib-arc-select-btn {
top: 1px;
}
#doclib-panel-chats .doclib-chip-scroll-arrow {
top: calc(50% - 9px) !important;
}
[data-doclib-panel="documents"] .doclib-chip-scroll-arrow {
top: calc(50% - 9px) !important;
}
#doclib-modal .doclib-chip-scroll-arrow {
top: calc(50% - 19px) !important;
}
[data-doclib-panel="documents"] #doclib-tidy-btn,
[data-doclib-panel="documents"] #doclib-select-btn {
top: 1px;
}
#doclib-panel-research #doclib-research-tidy-btn,
#doclib-panel-research #doclib-research-select-btn {
top: -0.5px;
}
#doclib-panel-research .library-sort-picker { top: -0.5px; }
[data-doclib-panel] { padding-top: 6px !important; }
#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; }
#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; }
#doclib-panel-chats .memory-search-input,
#doclib-panel-archive .memory-search-input {
height: 30px !important;
min-height: 30px !important;
flex-shrink: 0;
font-size: 11px;
}
/* Unified search bar across Library tabs + Memory modal — same height,
same magnifying-glass icon at the start, consistent padding. */
#doclib-panel-chats .memory-search-input,
#doclib-panel-archive .memory-search-input,
#doclib-panel-research .memory-search-input,
[data-doclib-panel="documents"] .memory-search-input,
#memory-modal .memory-search-input,
#tasks-modal .memory-search-input {
height: 30px;
min-height: 30px;
font-size: 11px;
background-image: url("data:image/svg+xml;utf8,");
background-repeat: no-repeat;
background-position: 9px center;
padding-left: 28px;
}
#doclib-panel-chats .doclib-chip {
height: 28px;
}
#doclib-panel-archive .doclib-chip {
height: 28px;
}
#doclib-panel-chats .doclib-chip,
#doclib-panel-archive .doclib-chip {
display: inline-flex;
align-items: center;
font-size: 9px;
padding: 0 8px;
border-radius: 10px;
}
.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) {
display: none;
}
/* Hide everything except the grid when a card is expanded */
.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) {
opacity: 0;
max-height: 0;
overflow: hidden;
pointer-events: none;
margin: 0 !important;
padding: 0 !important;
transition: opacity 0.12s ease, max-height 0.2s ease;
}
.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar {
display: none;
}
.admin-card:has(.doclib-card-expanded) > .doclib-grid,
.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list {
max-height: none !important;
min-height: 0 !important;
overflow: visible !important;
}
/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the
list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no
residual flex-gap leaves an empty strip above the reader — and zero the
admin-card gap so the grid/reader starts flush at the top. */
#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) {
display: none !important;
}
#email-lib-modal .admin-card:has(.doclib-card-expanded),
#email-lib-modal.email-reading .admin-card {
gap: 0 !important;
padding: 0 !important;
margin-bottom: 0 !important;
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
}
/* Override for chat-rows + research-rows: those expand inline with a
bounded preview (max-height: 60vh), so the grid must stay clipped
and scrollable. Without this scoping the overflow:visible above lets
the expanded chat preview escape the grid and overlap whatever's
underneath/beside the modal (e.g., a docked sibling panel) — what
the user calls "merging with other windows". */
.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid {
overflow: hidden auto !important;
}
/* Email modal needs the grid to STAY a constrained flex container when
expanded — overflow:visible (set above for cookbook) lets content escape
instead of letting the expanded reader fill via flex:1. We keep the same
max-height:none + min-height:0 but flip overflow back so the reader
actually fills the modal. */
#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid,
#email-lib-modal.email-reading .admin-card > .doclib-grid {
overflow: hidden !important;
display: flex !important;
flex: 1 1 auto !important;
}
#email-lib-modal.email-reading #email-lib-grid > .date-section-header {
display: none !important;
}
#email-lib-modal.email-reading .doclib-modal-content {
min-height: var(--email-reading-modal-min-h, auto);
}
#email-lib-modal .doclib-card.doclib-card-expanded {
flex: 1 1 auto !important;
height: 100% !important;
/* Neutral frame on the active email — the accent border + glow felt
overbearing on desktop; the size jump alone is enough signal. */
border: 1px solid var(--border) !important;
box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important;
animation: none !important;
}
/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title
row down 6px / right 2px, bolds the subject, hides the timestamp from the
meta line (the reader header carries its own date), and pulls the prev/next
nav arrows in 4px. */
@media (min-width: 769px) {
#email-lib-modal .doclib-card-expanded .email-card-titlerow,
#email-lib-modal .doclib-card-expanded .email-card-titlerow > * {
position: relative;
top: 6px;
left: 2px;
}
/* Smaller subject — the reader header below already carries weight. */
#email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title {
font-weight: 700;
font-size: 12px;
}
/* A bit more breathing room around the title row so the smaller bold text
doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */
#email-lib-modal .doclib-card-expanded .email-card-titlerow {
padding: 4px 0 6px;
line-height: 1.35;
}
/* Nudge the date down/left in the meta row, and give the meta line +4px of
extra bottom space so the shifted date isn't clipped. */
#email-lib-modal .doclib-card-expanded .email-meta-date {
position: relative;
top: 4px;
left: 0;
}
#email-lib-modal .doclib-card-expanded .memory-item-meta {
padding-bottom: 4px;
}
#email-lib-modal .doclib-card-expanded .email-card-nav-arrows {
position: relative;
left: -4px;
}
}
/* Skills modal: same mechanism as email above. The default expanded-grid
rule (overflow:visible, no flex) lets the card expand inline to ~content
height instead of filling — which is why the skill preview stopped at
partial height. Flip the grid back to a constrained flex container and
let the expanded card fill it, exactly like the email reader. */
#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid {
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
flex: 1 1 auto !important;
}
#memory-modal .doclib-card.doclib-card-expanded {
flex: 1 1 auto !important;
height: 100% !important;
}
#email-lib-modal .modal-content {
transition: width 0.22s ease-out, height 0.22s ease-out;
}
@media (min-width: 769px) {
body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) {
min-height: min(560px, 85vh);
}
}
/* Serve panel specifically: the admin-card inline style is
`overflow:hidden` (so the toolbar/header don't drift), and the list
inside has overflow:visible. On short windows that combination
clipped the cards off the bottom with no scrollbar. Make the list
itself the scroll surface so the rest of the card stays put. */
.cookbook-group[data-backend-group="Serve"] > .admin-card {
min-height: 0;
}
.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list,
.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list {
flex: 1 1 0;
min-height: 0;
overflow-y: auto !important;
overscroll-behavior: contain;
}
.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded {
flex: 0 0 auto !important;
overflow: visible !important;
}
/* Launch model cards use the same domino entrance as the other library lists
when a cached-model scan finishes. The class is removed after the one-shot
entrance, so filtering and sorting stay instantaneous. */
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card {
animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards;
}
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; }
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; }
@media (prefers-reduced-motion: reduce) {
#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card {
animation: none;
}
}
/* Drag-and-drop visual hint for the email compose pane. Subtle accent
outline + tinted overlay so it's obvious files will attach if dropped. */
.doc-editor-pane.email-dragover {
outline: 2px dashed var(--accent, #2563eb);
outline-offset: -8px;
background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent);
}
.doc-editor-pane.email-dragover::after {
content: 'Drop to attach';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--accent, #2563eb);
color: #fff;
padding: 8px 16px;
border-radius: 6px;
font-size: 12px;
pointer-events: none;
z-index: 1000;
}
/* Email reader window — make the body always fill the available vertical
space regardless of how the window is positioned/resized. The user can
drag the bottom-right corner to resize; the body re-flexes automatically. */
.email-window-modal .modal-content {
display: flex !important;
flex-direction: column !important;
resize: both;
overflow: hidden;
min-width: 320px;
min-height: 240px;
/* When user resizes/drags so the window covers a side, force the inner
body to fill all the way to the bottom. The default max-height: 85vh
(set inline by _makeDraggable's exitFullscreen) and 80vh from the
opener both cap at the *viewport* — if the browser window then changes
size (e.g. OS aero-snap), the modal stays at the old height and
truncates rows. Drop the cap whenever an explicit width OR height has
been written inline by the resize handle / drag handler. */
}
.email-window-modal .modal-content[style*="width"],
.email-window-modal .modal-content[style*="height"] {
max-height: calc(100vh - 16px) !important;
}
.email-window-modal .email-window-body {
flex: 1 1 auto !important;
min-height: 0 !important;
/* Explicit basis of 0 makes the body grow to fill remaining space rather
than its content's intrinsic height (which was capping the thread). */
flex-basis: 0 !important;
max-height: none !important;
overflow: auto !important;
overscroll-behavior: contain;
}
/* Individual email reader window — fullscreen mode (drag-to-top snap or
double-click header). Same recipe as the library modal: pin to viewport
edges and let the inner body absorb the remaining height. */
body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content {
position: fixed !important;
inset: 0 !important;
left: 0 !important;
top: 0 !important;
width: 100vw !important;
max-width: 100vw !important;
height: 100vh !important;
max-height: 100vh !important;
border-radius: 0 !important;
transform: none !important;
display: flex !important;
flex-direction: column !important;
}
body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body {
flex: 1 1 auto !important;
min-height: 0 !important;
max-height: none !important;
}
/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the
cookbook treatment so the toolbar and bulk actions stay reachable).
Leaves whichever navigation strip is currently visible on the left
(the wide sidebar OR the icon rail — they're mutually exclusive, so
one of the two CSS vars is 0 at any given time, set by init.js). */
body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content {
position: fixed !important;
top: 0 !important;
left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important;
right: 0 !important;
bottom: 0 !important;
width: auto !important;
max-width: none !important;
height: 100vh !important;
max-height: 100vh !important;
border-radius: 0 !important;
transform: none !important;
}
/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full-
bleed rule above squares off the corners and offsets the email by the icon
rail. On phones the email should be the normal bottom-sheet — full width,
rounded top corners. Same specificity as the rule above + later in source,
so it wins on mobile. */
@media (max-width: 768px) {
body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content {
left: 0 !important;
right: 0 !important;
width: 100vw !important;
max-width: 100vw !important;
height: 100dvh !important;
max-height: 100dvh !important;
border-radius: 14px 14px 0 0 !important;
border-top: 1px solid var(--border) !important;
}
}
/* Make the inner panes actually grow to fill the fullscreen container
instead of staying at their natural size. The body owns the remaining
height below the header; the admin-card + grid then expand into it. */
#email-lib-modal.email-lib-fullscreen .modal-body {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow: hidden !important;
}
#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card {
flex: 1 1 auto !important;
min-height: 0 !important;
}
#email-lib-modal.email-lib-fullscreen .doclib-grid {
flex: 1 1 auto !important;
min-height: 0 !important;
max-height: none !important;
overflow: auto !important;
}
@media (max-width: 768px) {
/* Mobile email modal sizing — keep in sync with the rule earlier in the
file. 75dvh tall always so flex children (the expanded email reader)
have a real height to grow into. */
#email-lib-modal .modal-content {
max-height: 90dvh !important;
max-height: 90vh !important;
height: 90dvh !important;
height: 90vh !important;
}
/* Inner panes grow to fill the modal-content — without flex:1 on the
body, the expanded email reader sits in a tiny box because there's
nothing pushing it to take the remaining height. overflow:hidden +
min-height:0 lets each layer pass its constraints down. */
#email-lib-modal .modal-body,
#email-lib-modal .admin-card {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow: hidden !important;
max-height: none !important;
}
#email-lib-modal .doclib-grid {
flex: 1 1 auto !important;
min-height: 0 !important;
max-height: none !important;
overflow-y: auto !important;
}
/* modal-content keeps its scroll OFF here — the inner flex children
(doclib-grid for the list view, email-reader-body for the expanded
reader) own the scroll surfaces. Without this, double-scroll layouts
trap touches and the reader can't claim full height. */
#email-lib-modal .modal-content {
overflow: hidden !important;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
touch-action: pan-y;
}
/* Attachment row on phones: cap to ~2 rows so it never dominates the
reader. If more chips exist, the row scrolls vertically. Smaller chip
padding + max-width keeps each chip compact so two rows usually fit
everything anyway. */
#email-lib-modal .email-reader-atts {
padding: 4px 10px !important;
gap: 3px !important;
max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */
overflow-y: auto;
align-content: flex-start;
}
#email-lib-modal .email-attachment-chip {
padding: 1px 6px !important;
font-size: 10px !important;
max-width: min(240px, calc(100vw - 44px)) !important;
line-height: 18px !important;
}
}
/* Mobile: only ONE scroll surface inside the cookbook modal. The
modal-content is the scroller. Everything inside (cookbook-body,
cookbook-group, all the inner lists/panels) gets overflow: visible
so touch-pan never gets trapped in a nested scroller.
Without this, three levels of overflow:auto + max-height combinations
produce dead-zone areas where swipes do nothing. */
@media (max-width: 768px) {
#cookbook-modal .modal-content {
overflow-y: auto !important;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
touch-action: pan-y;
}
#cookbook-modal .cookbook-body,
#cookbook-modal .cookbook-group,
#cookbook-modal .cookbook-section-body,
#cookbook-modal .hwfit-cached-list,
#cookbook-modal .doclib-grid,
#cookbook-modal .hwfit-list,
#cookbook-modal .hwfit-panel-cmd {
overflow: visible !important;
max-height: none !important;
min-height: 0 !important;
}
/* Running tmux output: cap how tall an expanded card gets (a long-lived job
can leave thousands of lines) so it doesn't balloon, but keep its own
scroll so you can read it. Outputs default collapsed on mobile now, so
you're usually looking at one expanded card at a time — no wall of nested
scrollers, and you collapse it to move past. */
#cookbook-modal .cookbook-output-pre,
#cookbook-modal .cookbook-task .cookbook-output-pre {
max-height: 45vh !important;
min-height: 0 !important;
overflow-y: auto !important;
overflow-x: hidden !important;
overscroll-behavior: auto; /* chain to the modal at the scroll boundary */
}
/* cookbook-body's flex:1 was needed when it owned scrolling — drop it
so the inner content drives modal-content's scroll height. */
#cookbook-modal .cookbook-body {
flex: 0 0 auto !important;
height: auto !important;
}
/* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0`
(so it fills the modal on desktop). On mobile the parent now has
`height: auto`, which collapses `flex: 1 1 0` to ZERO PX —
models render but the list area is invisible because the flex
basis is 0 and there's no free space to grow into. Switch to
content-sized flex so the list grows with its children. */
#cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list,
#cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list {
flex: 0 0 auto !important;
overflow: visible !important;
}
}
#cookbook-modal .hwfit-cached-list {
flex-shrink: 0;
}
.memory-toolbar {
transition: opacity 0.12s ease, max-height 0.2s ease;
max-height: 120px;
}
.doclib-card {
background: color-mix(in srgb, var(--fg) 3%, transparent);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
transition: all 0.15s, opacity 0.3s, max-height 0.3s ease;
position: relative;
}
.doclib-card.doclib-card-deleting {
opacity: 0;
max-height: 0 !important;
overflow: hidden;
margin: 0;
padding: 0;
border-color: transparent;
}
.doclib-card.selected,
/* Universal selection highlight: any card-shaped row (documents, chats,
archive, research, skills, memories, tasks) gets the same accent outline
when its checkbox is checked. Done via :has() so the highlight follows
selection without per-renderer JS changes. The canonical checkbox class
is .memory-select-cb across every list. */
.memory-item:has(.memory-select-cb:checked),
.doclib-card:has(.memory-select-cb:checked),
.doclib-chat-row:has(.memory-select-cb:checked) {
border-color: color-mix(in srgb, var(--red) 40%, transparent);
background: color-mix(in srgb, var(--red) 4%, transparent);
/* 2px accent outline overlaid on the 1px border — reads as a thicker
selected border without shifting layout (every card would otherwise
need a 2px transparent border to keep the same width). */
outline: 2px solid var(--accent-primary, var(--red));
outline-offset: -1px;
}
.doclib-card-header {
display: flex;
align-items: center;
padding: 7px 8px;
gap: 6px;
min-width: 0;
}
/* Mobile only: push the unexpanded email card's title-row text down 4px so
it sits visually centered with the surrounding card padding. Targets the
actual title-row class (.email-card-titlerow) — the earlier attempt used
.doclib-card-header which the email card builds differently. */
@media (max-width: 768px) {
#email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow {
margin-top: 4px;
}
}
.doclib-card-title {
font-size: 11px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
flex: 1;
}
.doclib-card-ver {
padding: 1px 6px;
border-radius: 8px;
background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent);
color: var(--accent, var(--red));
font-size: 9px;
flex-shrink: 0;
font-weight: 600;
letter-spacing: 0.3px;
min-width: 24px;
text-align: left;
box-sizing: border-box;
}
.doclib-card-ver-muted {
background: color-mix(in srgb, var(--fg) 6%, transparent);
color: color-mix(in srgb, var(--fg) 35%, transparent);
}
.doclib-card-lang {
padding: 1px 6px;
border-radius: 8px;
background: color-mix(in srgb, var(--accent) 10%, transparent);
color: var(--accent);
font-size: 9px;
text-align: left;
flex-shrink: 0;
min-width: 52px;
box-sizing: border-box;
}
.doclib-card-session {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 60px;
width: 100px;
max-width: 100px;
font-size: 9px;
color: var(--fg-muted);
flex-shrink: 0;
text-align: left;
}
.doclib-card-time {
flex-shrink: 0;
opacity: 0.5;
font-size: 9px;
color: var(--fg-muted);
min-width: 50px;
text-align: left;
}
/* Footer — used by archive cards */
.doclib-card-footer {
display: contents;
}
/* Preview — hidden by default, shown on expand */
.doclib-card-preview {
display: none;
position: relative;
padding: 8px 12px;
font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace;
font-size: 9.5px;
line-height: 1.5;
color: var(--code-fg, var(--hl-fg, var(--fg)));
border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
margin: 0;
}
.doclib-card-preview pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
.doclib-card-expanded .doclib-card-preview pre {
flex: 1 1 0;
overflow-y: auto;
min-height: 0;
}
.doclib-card-preview code.hljs {
background: none;
padding: 0;
}
/* Expanded-only action bar — inside preview. Buttons shifted up 4px by
trimming the row's top margin so they sit closer to the preview text. */
.doclib-card-expanded-actions {
display: none;
align-items: flex-start;
gap: 6px;
padding: 8px 0 2px;
border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
margin-top: 4px;
}
.doclib-card-expanded-actions > .doclib-card-action-btn,
.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn {
position: relative;
top: -4px;
}
.doclib-action-group {
display: flex;
flex-direction: column;
gap: 3px;
}
.doclib-action-btn-row {
display: flex;
gap: 6px;
}
.doclib-action-hint-row {
display: flex;
gap: 6px;
}
/* Match the chat/research footer buttons exactly: flat, bordered,
app font, accent on hover. */
.doclib-card-action-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
box-sizing: border-box;
font-size: 10px;
padding: 3px 8px;
border-radius: 4px;
background: none;
border: 1px solid var(--border);
color: var(--fg-muted);
cursor: pointer;
/* These buttons live inside .doclib-card-preview, which forces a
monospace font. The chat/research footer buttons instead inherit the
modal font, which is the app font (Fira Code) on mobile and Inter on
desktop (.modal-content's Inter rule is inside @media min-width:769px).
Mirror that here so all three footers match in both contexts. */
font-family: var(--font-family, 'Fira Code', monospace);
transition: border-color 0.15s, color 0.15s;
}
@media (min-width: 769px) {
.doclib-card-action-btn {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
letter-spacing: -0.015em;
}
}
.doclib-card-action-btn:hover {
border-color: var(--accent, var(--red));
color: var(--accent, var(--red));
}
.doclib-card-text-btn-danger {
color: var(--color-danger, #e06c75);
border-color: var(--color-danger, #e06c75);
}
.doclib-card-text-btn-danger:hover {
color: #ff4d4d;
border-color: #ff4d4d;
}
.doclib-card-expanded-actions > .doclib-card-action-btn {
font-size: 10px;
padding: 3px 8px;
}
/* Push the Open/Clone group to the right edge of the action bar — Delete
anchors the left, primary actions sit opposite. */
.doclib-card-expanded-actions > .doclib-action-group {
margin-left: auto;
}
.doclib-card-expanded-actions > .doclib-expanded-delete-btn,
.doclib-card-expanded-actions > .doclib-expanded-archive-btn {
left: 0 !important;
}
.doclib-expanded-mobile-more { display: none; }
.doclib-btn-hint {
font-weight: normal;
opacity: 0.35;
font-size: 8px;
min-width: 58px;
padding: 0 8px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.doclib-btn-hint { display: none !important; }
}
/* Chat row expand preview — matches the documents-tab expand style. */
.doclib-card-chevron {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: center;
flex-shrink: 0;
width: 14px;
height: 14px;
line-height: 0;
opacity: 0.3;
transform: rotate(0deg);
transition: transform 0.2s ease, opacity 0.15s;
}
.doclib-card-chevron svg { display: block; }
.doclib-card-expanded .doclib-card-chevron,
.task-card.expanded .doclib-card-chevron {
opacity: 0.8;
transform: rotate(180deg);
}
.doclib-card > .memory-item-actions { order: 1; }
.doclib-card > .doclib-card-chevron { order: 2; }
.doclib-chat-header .memory-item-actions,
.task-card .memory-item-actions { order: 2; }
.doclib-chat-header .doclib-card-chevron,
.task-card .doclib-card-chevron { order: 3; }
.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; }
.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; }
#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; }
#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; }
.doclib-chat-row { flex-direction: column; align-items: stretch; }
.doclib-chat-row .doclib-card-chevron {
display: inline-flex;
opacity: 0.55;
transition: transform 0.2s ease, opacity 0.15s;
flex-shrink: 0;
}
.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron {
transform: rotate(0deg);
}
.doclib-chat-row.doclib-card-expanded .doclib-card-chevron {
opacity: 0.8;
transform: rotate(180deg);
}
/* Release .memory-item's base max-height: 200px when a chat row is
expanded. Without this, the row clips at 200px on desktop and the
preview's messages/actions visually spill past the row's box —
"colliding" with the next chat in the column. The mobile takeover
below already handles this via the same override; this rule is the
desktop-or-any-viewport equivalent. */
.doclib-chat-row.doclib-card-expanded {
max-height: none;
overflow: visible;
}
/* When a chat row is expanded, take over the grid the same way documents
do: hide siblings, hide the admin-card toolbar, and let this row plus
its preview claim the full available height. .memory-item's default
max-height: 200px must be overridden or the preview will be tiny. */
/* Mobile-only "card takeover" for an expanded chat — hides siblings,
hides the admin-card toolbar, and lets this row + its preview claim
the full available height. On desktop the expanded preview just
renders inline below the row with its messages constrained by
max-height + overflow:auto so the user gets a normal scroll. */
@media (max-width: 820px) {
.doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) {
display: none;
}
.admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) {
display: none;
}
.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid {
flex: 1 1 auto !important;
max-height: none !important;
overflow-y: auto !important;
}
.doclib-chat-row.doclib-card-expanded {
flex: 1 1 auto !important;
max-height: none !important;
min-height: 0 !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
align-items: stretch !important;
}
.doclib-chat-row.doclib-card-expanded .doclib-chat-header {
flex: 0 0 auto !important;
}
.doclib-chat-row.doclib-card-expanded .doclib-chat-preview {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
padding-bottom: 4px !important;
}
.doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages {
-webkit-overflow-scrolling: touch;
max-height: none !important;
flex: 1 1 auto !important;
min-height: 0 !important;
}
}
.doclib-chat-preview {
font-size: 11px;
padding: 0 4px 6px;
}
.doclib-chat-preview .doclib-chat-open-btn {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
padding: 3px 8px;
border-radius: 4px;
color: var(--fg-muted);
border: 1px solid var(--border);
background: none;
cursor: pointer;
font-family: inherit;
transition: border-color 0.15s, color 0.15s;
}
.doclib-chat-preview .task-completed-report-btn {
color: var(--accent, var(--red));
border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border));
}
.doclib-chat-preview .doclib-chat-open-btn:hover {
border-color: var(--accent, var(--red));
color: var(--accent, var(--red));
}
.doclib-chat-preview-messages {
margin-top: 6px;
max-height: 320px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding-right: 4px;
}
/* Desktop expanded state — match the documents-tab behavior: hide
sibling chats, hide the admin-card toolbar/header, and let the
expanded row + its messages preview claim the full modal height.
Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class)
so the document-tab's global rules at line 12284-12304 skip them —
these mirror those rules scoped to chat rows. The mobile media query
above (820px) keeps the same takeover behavior for phones. */
@media (min-width: 821px) {
.doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) {
display: none;
}
.admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) {
display: none;
}
.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid {
flex: 1 1 auto !important;
max-height: none !important;
overflow: hidden auto !important;
}
.doclib-chat-row.doclib-card-expanded {
flex: 1 1 auto !important;
max-height: none !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
.doclib-chat-row.doclib-card-expanded .doclib-chat-preview {
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
.doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages {
flex: 1 1 auto !important;
min-height: 0 !important;
max-height: none !important;
overflow-y: auto !important;
}
}
.doclib-chat-preview-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
padding: 2px 0 2px;
border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
margin-top: 2px;
flex-shrink: 0;
/* Whole action footer nudged down 5px. */
position: relative;
top: 5px;
}
.doclib-chat-delete-btn,
.doclib-chat-archive-btn,
.doclib-chat-restore-btn,
.doclib-chat-discuss-btn,
.doclib-chat-copy-btn {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
padding: 3px 8px;
border-radius: 4px;
background: none;
cursor: pointer;
font-family: inherit;
transition: border-color 0.15s, color 0.15s;
}
.doclib-chat-archive-btn,
.doclib-chat-restore-btn,
.doclib-chat-discuss-btn,
.doclib-chat-copy-btn {
color: var(--fg-muted);
border: 1px solid var(--border);
}
.doclib-chat-restore-btn:hover {
color: var(--accent, var(--red));
border-color: var(--accent, var(--red));
}
.doclib-chat-archive-btn:hover,
.doclib-chat-discuss-btn:hover,
.doclib-chat-copy-btn:hover {
color: var(--accent, var(--red));
border-color: var(--accent, var(--red));
}
.doclib-chat-delete-btn {
color: var(--color-danger, #e06c75);
border: 1px solid var(--color-danger, #e06c75);
}
.doclib-chat-delete-btn:hover {
color: #ff4d4d;
border-color: #ff4d4d;
}
/* When a chat is archived there's no Archive button, so Delete becomes
the only left-side action and needs the auto margin to push Open right. */
.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn {
margin-right: auto;
}
/* Hide the "..." menu while the chat card is expanded — archive +
delete live in the preview footer instead. */
.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; }
.doclib-chat-preview-actions.doclib-chat-preview-actions-top {
border-top: none;
border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
margin-top: 4px;
margin-bottom: 6px;
padding: 0 0 6px;
justify-content: flex-start;
}
.doclib-chat-preview-messages .doclib-chat-msg {
margin: 4px 0 10px;
padding-left: 8px;
border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent);
}
.doclib-chat-preview-messages .doclib-chat-msg.user {
border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent);
}
.doclib-chat-preview-messages .doclib-chat-msg-role {
font-size: 9px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
opacity: 0.55;
margin-bottom: 2px;
}
.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role {
color: var(--accent, var(--red));
opacity: 0.85;
}
.doclib-chat-preview-messages .doclib-chat-msg-body {
white-space: pre-wrap;
word-break: break-word;
opacity: 0.85;
line-height: 1.45;
}
/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug
right with an accent tint; assistant bubbles hug left with a neutral
panel tint and a small model tag at the top. */
.doclib-chat-bubble-row {
display: flex;
margin: 6px 0;
}
.doclib-chat-bubble-row.user { justify-content: flex-end; }
.doclib-chat-bubble-row.assistant { justify-content: flex-start; }
.doclib-chat-bubble {
max-width: 85%;
padding: 6px 10px 8px;
border-radius: 14px;
font-size: 11px;
line-height: 1.45;
border: 1px solid var(--border);
background: color-mix(in srgb, var(--fg) 4%, transparent);
}
.doclib-chat-bubble-row.user .doclib-chat-bubble {
background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent);
border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent);
border-bottom-right-radius: 4px;
}
.doclib-chat-bubble-row.assistant .doclib-chat-bubble {
border-bottom-left-radius: 4px;
}
.doclib-chat-msg-model {
display: inline-block;
font-size: 8px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0.55;
margin-bottom: 3px;
padding: 1px 6px;
border-radius: 6px;
background: color-mix(in srgb, var(--fg) 8%, transparent);
color: var(--fg-muted, var(--fg));
}
.doclib-chat-bubble-body { word-break: break-word; }
.doclib-chat-bubble-body p { margin: 4px 0; }
.doclib-chat-bubble-body p:first-child { margin-top: 0; }
.doclib-chat-bubble-body p:last-child { margin-bottom: 0; }
.doclib-chat-bubble-body pre {
font-size: 10px;
margin: 4px 0;
padding: 6px 8px;
background: color-mix(in srgb, var(--bg) 60%, transparent);
border-radius: 6px;
overflow-x: auto;
}
.doclib-chat-bubble-body code {
font-size: 10px;
padding: 1px 4px;
border-radius: 3px;
background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.doclib-chat-bubble-body pre code { background: none; padding: 0; }
.doclib-chat-bubble-body ul,
.doclib-chat-bubble-body ol {
margin: 4px 0;
padding-left: 18px;
}
.doclib-card-collapse-chevron {
display: none;
align-items: center;
justify-content: center;
width: 24px;
height: 22px;
padding: 0;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: var(--color-muted, var(--fg));
font: inherit;
opacity: 0.3;
cursor: pointer;
flex-shrink: 0;
margin: 0 0 0 4px;
transition: opacity 0.15s, background 0.15s, border-color 0.15s;
}
.doclib-card-collapse-chevron:hover {
opacity: 1;
background: color-mix(in srgb, var(--fg) 8%, transparent);
border-color: color-mix(in srgb, var(--fg) 24%, transparent);
}
.cookbook-serve-title {
display: flex;
align-items: center;
min-width: 0;
}
.cookbook-serve-title .doclib-card-collapse-chevron {
display: inline-flex;
margin-left: auto;
margin-top: 0;
}
.doclib-card-expanded .doclib-card-collapse-chevron {
display: inline-flex;
}
/* Expanded card — fills the whole grid */
.doclib-card.doclib-card-expanded {
flex: 1;
min-height: 0;
background: color-mix(in srgb, var(--fg) 3%, transparent);
border: 1px solid var(--border);
border-radius: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
animation: doclib-expand 0.2s ease-out;
}
.doclib-card.doclib-card-expanded {
flex-direction: column !important;
align-items: stretch !important;
max-height: none !important;
gap: 4px !important;
}
.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; }
/* Non-preview children of an expanded doc card carry an inline flex:1 from
the row layout. With the card now flex-column those children would steal
half the height and shrink the preview — pin them at auto height. */
.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) {
flex: 0 0 auto !important;
}
/* The email reader IS the scroll container — keep its flex:1 so its
internal body can claim the rest of the card's height and scroll. */
.doclib-card.doclib-card-expanded > .email-card-reader {
flex: 1 1 auto !important;
min-height: 0 !important;
}
.doclib-card.doclib-card-expanded .doclib-card-preview {
display: flex;
flex-direction: column;
flex: 1 1 auto !important;
/* The preview matches chat rows: document content scrolls, while the
footer stays in the card rather than becoming the last scroll item. */
overflow: hidden;
min-height: 0;
}
.doclib-card.doclib-card-expanded .doclib-card-expanded-actions {
display: flex;
flex-shrink: 0;
}