The initial positioning used zoom-divided rect coordinates but the
visualViewport clamping block used raw rect.bottom/rect.top without
dividing by zoom. At ui-scale-125 the clamp pushed dropdowns off-screen.
Affected cookbook task menu-btn and serve-config dropdown menus.
getBoundingClientRect() returns zoom-scaled coords, but
window.innerWidth is unscaled. rect.right/z computed offset was
wildly negative at ui-scale-125, pushing dropdowns off-screen left.
Both createSessionItem() dropdown (rename/delete/archive) and
_showDropdown() (archive/research library cards) affected.
_startDrag(): divide getBoundingClientRect().left/.top by zoom factor
(right/left dock paths too). Also guard e.target.closest() calls
against Text nodes that lack the method — prevents close/minimize
buttons from failing the skipSelector check and triggering a drag
on click.
Removed the 120-char snippet preview from the _sendToChat() reference.
The injected text is now clean: [Title](#document-<id>). Also captured
Deep Research 'Send to Chat' reference injection feature to task board.
_handleMarkdownPreviewClickHint() now no-ops. The toast appeared after
3 rapid clicks in the markdown preview and told the user to click Write to
edit — annoying for users who click-drag to highlight while reading. Users
self-hosting Odysseus know how preview/write modes work.
.msg-user has width:fit-content which collapses when body content is replaced
by a textarea. Added .msg-user.editing CSS rule with width:85% !important,
and JS adds/removes 'editing' class during edit flow.
app.js: overflow plus-menu positionMenu() — divide r.left/r.top by zoom
cookbookRunning.js: hamburger dropdown — use window.innerWidth - (rect.right/z)
cookbookServe.js: 5 fixes — toolbar dropdown, launch-more, split-arrow, GPU popup, saved-menu
skills.js: kebab tool menu — divide rect bounds by zoom
colorPicker.js: picker position() — divide rect coords by zoom
emojiPicker.js: picker open — divide rect coords by zoom
theme.js: zone highlights — divide all rect values by zoom
Adds a 'Chat' button to the left of the Save button in the document
editor footer. Clicking it injects a reference into the chat textarea:
[Title](#document-<id>) — <120-char snippet>...
The reference is not the full document content — just a title link
with opening snippet. The user can then send it or edit the message
before sending. This is the first step toward making documents feel
like first-class citizens in agent chat.
getBoundingClientRect() returns zoom-scaled coordinates but
window.innerWidth does not. With ui-scale-125 active, the right-edge
calculation (window.innerWidth - rect.right) mixed coordinate spaces,
causing dropdowns to appear displaced from their hamburger buttons.
Divide window.innerWidth by the computed zoom factor to match the
rect coordinate space. Fixes both cookbookRunning.js (Active tab)
and cookbookServe.js (Serve tab).
CSS: added comprehensive zoom compensation for ALL modal-content
variants (memory, tasks, preset, doclib, gallery, notes-pane,
research, compare, email-lib) and a safety net for user-resized
windows carrying inline height styles.
JS (windowResize.js): _uiZoom() helper reads computed zoom from :root.
Restore path divides saved localStorage dimensions by zoom factor.
Resize-move viewport clamps use zoom-adjusted vh/vw so dragging
never pushes the window beyond the visible area.
Also added docs/improvement-tasks.md as the Ulysses task board.
- Added persistent Scan button to the Serve tab toolbar (was only shown
in empty state, making it impossible to refresh when localStorage had
cached results)
- Hooked into background poll to clear model scan cache (key:
cookbook_cached_models_scan_v1) when downloads transition to done,
so the Serve tab doesn't show stale data after a download completes
Event handler for #hwfit-cache-scan already existed in the codebase but
the button element was never rendered in the HTML template.
Setting epSel.value triggered an async change event whose handler
called refreshModels('') — wiping the correct model selection that
refreshModels(settings.default_model) had just applied moments earlier.
The dropdown silently fell back to the alphabetically-first model
(deepseek-v4-flash instead of qwen-3.6-35B-A3B).
Moved the change listener registration to after the settings block
so the async change event fires before any listener exists. The
utility and teacher sections already followed this pattern.