The 2-slot dynamic action-button system reordered by recently-used
actions (localStorage) could push Copy into the "..." overflow menu
whenever 2+ other actions had been used more recently — reported as
Copy being entirely missing from both AI-message and user-message
footers. Copy is now rendered as a fixed, always-visible button
outside the recency rotation in both createMsgFooter and
createUserMsgFooter.
Localized (non-English) Gmail accounts return special-use mailbox names in
the account's own language, encoded as modified UTF-7. Folder resolution
previously guessed a mailbox's role by checking whether its raw name
contained an English substring like "sent"/"trash"/"archive" - this never
matches for e.g. a Russian-language Gmail account, so list_emails,
search_emails, and the web UI's own folder listing/select all silently
treated the Sent folder as nonexistent or unlabeled.
- mcp_servers/email_server.py: role_flags/candidates in _resolve_folder and
_folder_role_from_name now include "sent"; _list_emails/_search_emails
resolve the folder name before SELECT instead of using the literal name.
- routes/email_routes.py: same fix in the independently-duplicated
_resolve_mail_folder/_folder_role_from_name/_list_emails_sync used by the
REST API and web UI. Added _folder_role_from_flags() to read the actual
IMAP LIST response's special-use attributes (RFC 6154 + Gmail
extensions), which are locale-independent, and expose them via a new
roles field on GET /api/email/folders.
- static/js/emailInbox.js, static/js/emailLibrary.js: folder-tab role/label
logic now prefers the server-supplied role over guessing from the name,
falling back to the old substring guess only when no role is provided
(fixture/stale-cache/timeout response paths).
* feat(models): define capability schema and readers
* fix(models): harden Google catalog probing
Restrict native catalog probing to the Gemini host, keep provider keys out of request URLs, filter non-chat model resources, and preserve the manual refresh default in the built-in Google add flow.
* docs: update static/js/MODULE_SUMMARY.md to reflect current ES6 frontend
Rewrite the stale module summary to match the current no-build,
ES6-module frontend architecture. Adds coverage of app.js orchestration,
the chat/SSE pipeline (chat.js, chatStream.js, chatRenderer.js,
streamingRenderer.js), new subsystems (research/, compare/, document
streaming, cookbook*, skills.js), and removes the obsolete <script> load
order assumptions.
* cleanup: remove dead MEMORY_DOC / memory_doc paths (closes#4411)
Removes the unused MEMORY_DOC constant and the matching DataConfig
memory_doc field / set_data_paths entry. No runtime code imports or
references these paths, so this is a no-behavior-change dead-code
cleanup under the storage-architecture tracker #4377.
* fix(chat): Expand user chat bubble edit textbox width
- Update user chat bubble width from `fit-content` to `85%` to ensure consistency with the AI chat bubble edit textbox width.
* style(chat): Refine user message bubble width logic
- Change general bubble width to `fit-content`
- Set width to 85% specifically for user messages containing a `textarea`
Align regression tests with the current Odysseus behavior after merging origin/dev into local main.
- keep phone/name-only contacts valid and cover null email without crashes
- pin explicit web-search false form submission in chat.js
- update Cookbook dependency/download completion tests for combined live + persisted output
- expose SGLang OS package repair hints from backend diagnosis
- treat MLX and MLX-community repos as servable on Apple Metal while keeping CUDA behavior unchanged
- keep desktop new-chat coverage on the shared preferred-model helper
- remove a hardcoded crop overlay portal z-index literal
- include the local agent-loop cleanup that removes the old manage_notes reminder repair shim
Verified with: docker run --rm -v /home/pewds/odysseus-cookbook-fresh:/app -w /app odysseus-cookbook-fresh-odysseus python3 -m pytest -q (4515 passed, 4 skipped).
The comparison grid hard-codes 2-4 equal columns with no phone breakpoint, so at
390px two models get ~178px columns and four get ~88px columns. Each column is a
full scrolling chat, so content is unreadably over-wrapped and clipped. On
phones (<=768px), stack the panes into a single scrollable column. Desktop is
unaffected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.