Commit Graph

100 Commits

Author SHA1 Message Date
pewdiepie-archdaemon
966b53df77 Improve Cookbook serve diagnostics and recommendations 2026-06-02 12:15:47 +09:00
NovaUnboundAi
3319310942 Allow longer deep research extraction timeouts (#651)
Co-authored-by: NovaUnboundAi <NovaUnboundAi@users.noreply.github.com>
2026-06-02 11:50:03 +09:00
Achilleas90
247df16e82 Fix ordered list rendering in markdown preview (#645) 2026-06-02 11:49:44 +09:00
Rasmus
1882ad68ea fix: open #document deep-links on refresh and surface load errors (#631)
Add a hashchange handler for #document-<id> so refresh / URL-bar nav opens the document, and replace the silent console.error in loadDocument with a user-facing toast.

Closes #560
2026-06-02 11:48:54 +09:00
Christopher Milian
35ba56fa0c fix: remove ollama backend filter conflict (#613) 2026-06-02 11:48:35 +09:00
nsgds
5645cce6d0 Support vLLM 0.20.2 / NIM reasoning-parser output end-to-end (surface + agent context + render) (#602)
* fix(stream): read 'reasoning' SSE field for vLLM 0.20.2 / NIM

vLLM 0.20.2 / NVIDIA NIM emit reasoning-parser output in the `reasoning` delta field; older builds use `reasoning_content`. stream_llm() read only the latter, so reasoning from models like Nemotron-3-Nano (--reasoning-parser) was silently dropped and never rendered. Accept either field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(agent): keep reasoning_content only on the latest assistant turn

The agent loop echoed each round's reasoning back as `reasoning_content` on every assistant turn, assuming vendors ignore it. Nemotron's chat template re-injects ALL prior reasoning_content as <think> blocks, and the loop is trimmed only once (before it starts) — so reasoning accumulated unbounded across rounds, bloating context and feeding the model its own prior reasoning, which reinforced repetition/looping. Strip reasoning_content from earlier assistant turns so only the most recent round carries it (still satisfies DeepSeek's thinking-mode follow-up requirement).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(agent-ui): wrap each round's reasoning in its own <think> block

The streamed think-tag wrapper gated on whole-message substring checks (accumulated.includes('<think>')), which only ever wrapped ONE reasoning block per message. A multi-round agent response has a reasoning phase per round, so once round 1 closed its <think>...</think>, rounds 2+ reasoning was emitted unwrapped and leaked into the visible answer. Replace the substring checks with a stateful open/close flag that toggles per think/answer cycle, so each round's reasoning gets its own collapsible block. Single-turn chat is unchanged (one open, one close).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(stream): reasoning/reasoning_content delta surfaces as thinking chunk

Covers @pewdiepie-archdaemon's requested regression: a streamed {reasoning: ...} delta emits a thinking chunk while {content: ...} streams as normal content; plus the older reasoning_content field for backward compat. Mirrors the #591 scenario.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:48:17 +09:00
nsgds
a857d2016d fix: don't bill self-hosted models reached by a container/service hostname (#596)
* fix(cost): treat dotless container hostnames as local (free)

getModelCost() substring-matches model names against a cloud price table, so a self-hosted 'nemotron'/'llama' model was billed at cloud rates. isLocalEndpoint() only recognized IPs / localhost / .local, not bare Docker service names (nim-nano, llamaswap), so the local-is-free guard missed them. A single-label hostname (no dot) can never be a public API -> treat as local.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(cost): isLocalEndpoint classifies service names local, cloud FQDNs billable

Covers @pewdiepie-archdaemon's requested cases: llamaswap/nim-nano + localhost/private-IPs/.local => local (free); api.openai.com/openrouter.ai/etc => not local. Drives the real function via node --input-type=module (same approach as test_reply_recipients_js.py), skips when node is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:47:58 +09:00
william-napitupulu
649cacfa05 Importing files bug (#582)
* Update Styles.css

Small update to the styles that bothered me, i noticed in the window/modal for calendar when editing a day the time icons had a mask that overlapped the icon.  I simply added 'background-image: none' prop to it/

* Importing files bug

I found a bug that wouldn't let me upload files in the library window during the documents tab, when a user selected a file, the code grabbed a reference to fileInput.files and immediately cleared the input value (fileInput.value = '') to allow for re-uploading the same file later. However, because fileInput.files is a live FileList tied directly to the DOM element, clearing the input inherently emptied our saved variable as well, resulting in lost file data.

Note this error might be browser specific as it worked fine on Zen/Firefox but failed on Edge and chrome

Fix use Array.From which copies the value into files instead of using refrences
2026-06-02 11:47:25 +09:00
Sirsyorrz
cb3d86608c Cookbook: pick the correct vLLM tool-call-parser for Qwen2.5 (#580)
The model-name detector treated every Qwen model as a Qwen3, falling
into the qwen3_xml parser:

    if (n.includes('qwen3') && n.includes('coder')) return 'qwen3_coder';
    if (n.includes('qwen')) return 'qwen3_xml';   // catches qwen2.5 too

qwen3_xml is the parser for Qwen3 reasoning/instruct models. Qwen2.5
(and Qwen2, Qwen1.5) ship with hermes-style tool calling, so the
qwen3_xml parser never recognises their tool calls — they leak through
as plain text in the assistant reply and the agent silently fails to
execute anything.

Reproduces with:
  vllm serve Qwen/Qwen2.5-Coder-14B-Instruct-AWQ ... \
    --enable-auto-tool-choice --tool-call-parser qwen3_xml
  → ask the agent to call any tool → JSON shows up in chat, no call runs.

Fix the ordering:
  qwen3 + coder → qwen3_coder
  qwen3         → qwen3_xml
  qwen          → hermes   (Qwen2.5 / Qwen2 / Qwen1.5)

Verified against the model matrix:

  Qwen2.5-Coder-14B-Instruct-AWQ → hermes
  Qwen2.5-7B-Instruct            → hermes
  Qwen3-8B                       → qwen3_xml
  Qwen3-32B                      → qwen3_xml
  Qwen3-Coder-30B-A3B            → qwen3_coder
  Qwen2-72B-Instruct             → hermes
  Qwen1.5-7B-Chat                → hermes
2026-06-02 11:47:15 +09:00
ooovenenoso
15c7cb58e7 fix(cookbook): retry 0% HF download stalls sooner (#691)
Co-authored-by: Kevin <120500656+oooindefatigable@users.noreply.github.com>
2026-06-02 11:42:59 +09:00
Afonso Coutinho
634c16a019 fix: reply-all Cc's the user's own other addresses (multi-account) (#672)
* feat: publish all configured email addresses for reply-all exclusion

* fix: exclude all of the user's own addresses from reply-all, not just the active one

* test: reply-all excludes all of the user's configured addresses
2026-06-02 11:42:20 +09:00
James Arslan
6776c7d691 Surface silent model fallback instead of masking it (#868)
When the selected model fails before producing output, stream_llm_with_fallback
quietly switches to the next candidate and the reply is shown under the
originally selected model's name, so a misconfigured provider looks like it
works. (Concretely: a Bedrock gateway that 400s every Anthropic/Claude request
appears fine because another model silently answers under the Claude label.)

Emit a `fallback` SSE event ({selected_model, answered_by, reason}) the first
time a non-primary candidate produces output, forward it through the agent loop
and both chat-route paths, stamp the response metrics with the model that
actually answered, and show a notice + relabel the reply in the UI.

Tested: python -m pytest tests/test_llm_core_fallback.py (3 pass);
python -m py_compile src/llm_core.py src/agent_loop.py routes/chat_routes.py;
node --check static/js/chat.js.
2026-06-02 11:37:25 +09:00
BarsatZulkarnine
00f16d66a3 Fix test suite: ESM module loading and stub isolation (#844)
* Fix test suite: ESM loading and stub isolation (refs #605)

Three targeted fixes to reduce suite failures from 9 → 1:

1. package.json: add "type": "module" so Node loads static/js/**
   as ES modules. Fixes 7 tests in test_compare_js.py and
   test_reply_recipients_js.py that fail with
   "SyntaxError: Unexpected token 'export'".

2. test_null_owner_gates.py: add Base and ChatMessage to the
   core.database stub. Without Base the scheduler test cannot
   import at collection time; without ChatMessage core/__init__.py
   fails mid-load when session_manager.py tries to import it,
   leaving core partially initialised in sys.modules and poisoning
   the auth manager migration test that runs later in the same file.

3. test_task_scheduler_session_delivery.py: skip gracefully when
   core.database is stubbed (Base is a MagicMock) rather than
   crashing. The test passes correctly when run in isolation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Scope ESM declaration to static/js/ and document isolation workaround

Per review feedback on #844:

1. Move "type": "module" from root package.json to static/js/package.json.
   The root package.json had no type field (defaulted to CJS) and should
   stay that way — vendored UMD bundles in static/lib/ use require() internally
   and would break if Node ever tried to load them as ES modules. Node resolves
   the nearest package.json, so adding it in static/js/ scopes the ESM
   declaration to just the files the JS unit tests actually load
   (compare/state.js, emailLibrary/replyRecipients.js).

2. Expand the module-level skip comment in test_task_scheduler_session_delivery
   to document that it is a temporary isolation workaround, explain root cause
   (test_null_owner_gates installs a module-level sys.modules stub with no
   cleanup), record before/after suite numbers, and note the clean path
   (refactor to fixture-scoped stub).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 11:29:29 +09:00
CocoLng
8e918dfdbb Ignore AltGr keystrokes in Ctrl+Alt keyboard shortcuts (#825)
* Ignore AltGr keystrokes in Ctrl+Alt keyboard shortcuts

Browsers report AltGr (right Alt on AZERTY/QWERTZ and most non-US
layouts, used to type @ # { } [ ] | \ and the euro sign) as
ctrlKey+altKey. The default keybinds map destructive actions to
Ctrl+Alt+<letter> (delete_session, new_session, incognito,
open_calendar), so a non-US user typing a special character could
silently fire them.

Guard the shortcut matcher, the editor keydown handler, and the rebind
capture with getModifierState('AltGraph'), which is true for AltGr but
false for a genuine left Ctrl+Alt. macOS is excluded: there the Option
key legitimately sets AltGraph and there is no AltGr/Ctrl+Alt collision
to guard against, so the guard would otherwise break Ctrl+Option /
Cmd+Option shortcuts (notably in Firefox).

The detection lives in one place — isAltGrEvent / IS_MAC in
static/js/platform.js — and all three call sites route through it, so the
guards can't drift apart.

The editor handler only skips the Ctrl+Alt chord block, so layout
shortcuts reachable via AltGr (e.g. [ ] brush size = AltGr+5/+8 on
AZERTY) keep working.

* Require Ctrl+Alt for the AltGr guard and consolidate keybind test marks

isAltGrEvent now also checks ctrlKey+altKey so it only suppresses the
"AltGr reported as Ctrl+Alt" collision; an event asserting AltGraph on
its own (a Linux ISO_Level3_Shift layout, a stray modifier) is left
alone. Pin it with test_isaltgr_false_when_altgraph_set_but_not_ctrl_alt.

Collapse the 12 per-test node skipif marks into one module-level
pytestmark, and note in platform.js why IS_MAC intentionally covers
iPad/iPhone and mirrors the isMac checks in calendar.js / sessions.js.
2026-06-02 11:12:54 +09:00
Tatlatat
aba15e7b6d fix(cookbook): sort by Fit when the Fit header is clicked (#842) (#860)
The Cookbook Scan/Download (hwfit) table gave the Fit column key:'score', so
clicking the Fit header sorted by score instead of by fit. Give the Fit column
its own 'fit' sort key, add a matching option to the #hwfit-sort select, and
rank fit_level (perfect > good > marginal > too_tight > no_fit) in the
client-side sort. Default puts the best fit first; clicking again reverses it.
Score still sorts by score.

Closes #842
2026-06-02 11:09:18 +09:00
pewdiepie-archdaemon
96618b01c0 Polish task UI slash commands and Ollama serving 2026-06-02 09:36:03 +09:00
pewdiepie-archdaemon
ab0a480f30 Show Ollama models in Cookbook Serve 2026-06-02 07:38:45 +09:00
pewdiepie-archdaemon
cd53ad01e8 Clarify AI tasks and skipped activity rows 2026-06-02 07:11:40 +09:00
pewdiepie-archdaemon
ed946d8e61 Polish task activity icons 2026-06-02 07:04:52 +09:00
pewdiepie-archdaemon
1ff8669199 Compact mobile task controls 2026-06-02 07:02:26 +09:00
pewdiepie-archdaemon
7f9afe75e2 Remove mobile notes close button 2026-06-02 07:00:40 +09:00
pewdiepie-archdaemon
b7477d063a Clarify task status controls on mobile 2026-06-02 06:57:53 +09:00
pewdiepie-archdaemon
637c7511a2 Add model favorite dot feedback 2026-06-02 06:50:22 +09:00
pewdiepie-archdaemon
4a112175e2 Remove broken remind slash command 2026-06-02 06:48:41 +09:00
pewdiepie-archdaemon
d5c7e3d3e4 Add direct tool slash commands 2026-06-02 06:44:29 +09:00
pewdiepie-archdaemon
3959eec602 Refresh slash command hints 2026-06-02 06:40:23 +09:00
pewdiepie-archdaemon
3c1e0edea3 Polish model picker favorites 2026-06-02 06:33:53 +09:00
pewdiepie-archdaemon
e5cae37d15 Merge branch 'pr-673' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon
7242431335 Merge branch 'pr-738' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon
0ae30211d8 Merge branch 'pr-550' into visual-pr-playground 2026-06-02 06:26:32 +09:00
pewdiepie-archdaemon
6873b60721 Merge branch 'pr-594' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon
c1cb6f0d55 Merge branch 'pr-575' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon
664acf73ee Merge branch 'pr-469' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon
7ef7791ac8 Merge branch 'pr-684' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon
3224cd2ec7 Merge branch 'pr-668' into visual-pr-playground 2026-06-02 06:26:31 +09:00
pewdiepie-archdaemon
49ae46001c Merge branch 'pr-696' into visual-pr-playground 2026-06-02 06:26:31 +09:00
SurprisedDuck
b70ae56ffa Sanitize preserved markdown HTML
`mdToHtml` deliberately stashes literal <details> blocks and <a> tags from
the source text *before* the global HTML-escape pass and restores them
verbatim into the string callers assign to `innerHTML` (e.g. chatRenderer's
`b.innerHTML = ...processWithThinking(text)`). Nothing scrubbed those
fragments, so message/agent content containing
`<details><img src=x onerror=...></details>` or
`<a href="javascript:..." onmouseover=...>` executed arbitrary script in
the authenticated page.

Route both stashed fragments through `sanitizeAllowedHtml()`, which parses
them in an inert <template> (no resource loads, no script execution),
removes script-capable elements, and strips event-handler attributes plus
javascript:/vbscript:/data: URL schemes. Hardening details:

- Compare tag names case-insensitively and drop the SVG/MathML foreign-
  content roots. An SVG-namespaced <script> has the lower-case tagName
  'script', so an HTML-only upper-case check would miss it — a real bypass.
- Sanitize to a fixpoint (re-parse + re-clean until stable) to blunt
  mutation-XSS, where re-serializing/re-parsing reshapes the tree.

Benign anchors and <details> blocks are preserved unchanged.

Verified under jsdom against the obvious vectors plus mutation-XSS probes
(svg/math-namespaced <script>, foreignObject, ns-confusion, comment
breakout, template smuggling): no script/iframe element, event handler, or
javascript:/data: URL survives, and benign markup is kept.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-02 05:58:38 +09:00
SurprisedDuck
7a830e504d Escape email fold summary metadata
The email reader folds quoted history into <details> summaries via
`_foldSummary()` (static/js/emailLibrary/signatureFold.js), which builds a
sender/date "meta" chip into the summary HTML and assigns it to innerHTML.
The server-side thread parser (`_extract_quote_meta`,
src/email_thread_parser.py) strips tags but then un-escapes HTML entities
and preserves `<...>` patterns, and that raw meta reaches `_foldSummary`
unescaped via `_renderTurnsFromServer` (`t.meta`) — so an inbound email
whose quoted attribution contains `From: &lt;img src=x onerror=...&gt;`
runs script when the victim merely opens the message (stored XSS).

Make `_foldSummary` the single escaping chokepoint: escape `primary` and
`subMeta` with the module's existing `_esc`. The client-side
`_extractQuoteMeta` previously pre-escaped its output, and every consumer
of it routes through `_foldSummary`, so drop that now-redundant escaping to
avoid double-encoding (e.g. "Ben & Jerry" -> "Ben &amp;amp; Jerry").

Verified (jsdom): server-raw and client-extracted malicious metas yield 0
live elements and 0 event-handler attributes; benign "Ben & Jerry" renders
single-escaped.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-02 05:50:53 +09:00
Afonso Coutinho
5da662441c Validate slash command time minutes
* fix: reject hour > 23 in 'today/tomorrow' reminder time parsing

* fix: reject minute > 59 in reminder time parsing
2026-06-02 05:50:19 +09:00
Zeus-Deus
ad445a1b30 Improve accessibility across core flows (#86)
First incremental pass at issue #86, focused on the universal entry
points and primary navigation. All changes verified in-browser with the
axe-core engine (0 violations on the surfaces below) plus manual keyboard
testing, on both desktop (1280px) and mobile (390px).

Login / first-run setup (static/login.html)
- Add a real <h1>, wrap content in <main> + <footer> landmarks.
- Mark the decorative boat SVG aria-hidden.
- Errors now use role="alert" so screen readers announce them.
- "Remember me" checkbox is keyboard-focusable (was display:none) with an
  accessible name and a focus ring; dynamic 2FA field gets a linked label.
- Darken the brand-red submit button so white text clears WCAG AA 4.5:1
  (was ~3.2:1); add visible :focus-visible rings.

App shell (static/index.html, static/style.css)
- Remove invalid role="region" from the <main> chat container (it was
  overriding the implicit main landmark).
- Add a persistent, visually-hidden <h1> inside <main> so the page always
  exposes one logical level-1 heading — works even on mobile where the
  sidebar (with the visible brand) is hidden off-canvas.
- Add a reusable .a11y-visually-hidden utility.
- Raise chat-title, model-picker, settings-helper and notes text contrast
  above 4.5:1 (were 2.8-3.9:1).

Keyboard nav + dialogs (static/js/a11y.js - new)
- Make the click-only <div> sidebar navigation (New Chat, Search, Brain,
  Calendar, Compare, Cookbook, Deep Research, Gallery, Library, Notes,
  Tasks, Theme, account) focusable and Enter/Space-activatable, announced
  as buttons (skipping role=button where a nested control would create a
  nested-interactive violation). Visible focus ring reused from existing
  .list-item:focus-visible.
- Upgrade modals (.modal-content and the docked .notes-pane) to labelled
  role="dialog" + aria-modal, and normalise their title to heading level 2
  so heading order stays valid. A MutationObserver covers runtime-rendered
  rows and modals.

Decorative background canvases (static/js/theme.js)
- Mark all 7 bg-effect canvases aria-hidden.

Notes & Tasks (static/js/notes.js, static/js/tasks.js)
- Label the icon-only Note/To-do toggle pills (fixes a critical
  button-name issue) and track aria-pressed state.
- Improve Notes header-button + empty-state contrast.
- Give the Tasks sort <select> an accessible name (fixes a critical
  select-name issue).

Remaining data-dense tool modals (Tasks cards, Calendar, Gallery, Email,
Cookbook, Compare, Deep Research) still have muted-text contrast to polish
and are the next incremental step, per the issue's own guidance.
2026-06-01 22:04:00 +02:00
Zeus-Deus
a4c2a6990a Model picker: search + recent + favorites for large catalogs
Replace the flat dump of every model in the chat-input picker with a
quick-switch. Opening the picker now shows a search box, an auto-tracked
Recent list (last 5 picks), and a manual Favorites list instead of every
available model crammed into a 280px dropdown. With large catalogs
(e.g. OpenRouter's 350+ models) this was unusable as both a quick-switch
and a browser.

- Recent: each pick is recorded most-recent-first (capped at 5) under a
  new odysseus-model-recent key, so the next open has it one click away.
- Favorites: an inline star on every row toggles favorite state and
  writes the existing odysseus-model-favorites key, so the sidebar Models
  section stays in sync. The star toggles only — it never picks the model.
- Search filters a flat list across the whole catalog; favorited rows
  keep their filled star while filtered.
- Small catalogs (<=12 models) still list everything in browse mode so
  tiny installs aren't forced to search for a model.
- Touch friendly: stars are always visible (no hover-reveal) and tap
  targets grow on narrow screens.

No changes to sidebar visibility defaults.

Closes #399
2026-06-01 20:39:34 +02:00
Collin Osborne
471ee494f0 fix: make transient dropdown/popup menus close on Escape
The global Escape arbiter in ui.js only sees `.modal` elements, so the many
ad-hoc dropdowns and context popups that are built on the fly and appended to
<body> ignored Escape entirely: document-library card/chat menus, chat
context/stats/overflow popups, cookbook serve & running menus, calendar event
menus, and compare pane menus.

Add a small DOM-free dismissal registry (static/js/escMenuStack.js). Menus
register a dismiss callback while open, and the arbiter closes the
most-recently-opened one first, so a menu opened over a modal closes before the
modal. bindMenuDismiss() wires the ubiquitous "append-to-body, close on outside
click" idiom to both the outside-click listener and the Escape stack in one
call, and dismissOrRemove() lets the pre-existing bulk removers (scroll/swipe/
modal-dismiss cleanup, reopen sweeps) tear a menu down through its real teardown
instead of orphaning its stack entry.

Covers ~14 menus across documentLibrary, chatRenderer, cookbookServe,
cookbookRunning, calendar, and compare/panes. Every teardown path — item click,
outside click, swipe, toggle, rebuild, bulk cleanup — routes through the
registry so no entry is ever stranded.

tests/test_esc_menu_stack_js.py pins the registry's LIFO and
exactly-one-per-press guarantees (node-driven; skips when node is absent).
2026-06-01 14:23:22 -04:00
k.greyZ
7a3871fc95 feat(onboarding): improve setup UX with clickable triggers and auto-fill buttons
- Turn the "/setup" text on the welcome screen and fallback state into a clickable link that automatically runs the setup command.
- Add an interactive down-arrow "Use in Chat" button next to copy button on typewriter-generated setup code blocks.
- Programmatically trim the "..." placeholder when inserting API keys, focusing the cursor right after "sk-".
- Implement click-delegation for supported provider spans and raw code elements inside the setup guide to instantly pre-populate the input bar.
2026-06-01 21:11:47 +03:00
Zeus-Deus
b4b1d00cc5 Make tool windows resizable by dragging edges or corners
Library, Notes, and the other floating tool windows (Tasks, Calendar,
Gallery, Email, Cookbook, Brain, Settings, Theme, Compare, Research,
Sessions) could be moved and snapped but never resized — there were no
resize handles and dragging the edges did nothing.

Add a shared makeWindowResizable() helper and wire it into the existing
makeWindowDraggable() so every draggable window gains native-style
edge/corner resizing from one place:

- Grab any of the four edges or four corners to resize; the cursor
  reflects the active handle (ew/ns/nwse/nesw-resize).
- Detects pointer proximity to the border instead of injecting handle
  elements, so it works regardless of each window's overflow model
  (.modal-content scrolls its body; .notes-pane scrolls an inner el).
- Min-size clamp (320x200) and viewport clamping so a window can't be
  collapsed to nothing or dragged off-screen.
- Per-window size is remembered and restored on reopen.
- Disabled on mobile (windows are full-screen sheets there) and while a
  window is docked or fullscreen-snapped.
- Touch supported at tablet width and up; self-heals a missed pointer-up
  so a lost mouseup can't leave a window stuck in resize mode.
2026-06-01 19:49:23 +02:00
Sirsyorrz
853576273a Cookbook: make the GPU process popup actually visible
Two bugs hid the popup that opens on double-click (or right-click) of
a GPU button in the Serve panel:

1. z-index 240 vs the cookbook modal at 260 — popup rendered behind
   the modal it was spawned from.

2. Horizontal position was just `button.left`, with no clamp against
   the viewport. GPU buttons sit near the right edge of the modal, so
   the popup got anchored at a left that pushed most of its body past
   the viewport's right edge.

Switch the popup to position:fixed (escapes scrolling / transform
stacking contexts on any ancestor), bump z-index to 10010 (above the
themed-confirm / overlay layer that sits around 9000-10000), and
clamp left/top after measuring the rendered size — including flipping
above the button if there isn't room below. The popup is now fully
visible regardless of which GPU button it's anchored to or how
narrow the viewport is.
2026-06-02 01:23:06 +10:00
Zeus-Deus
21b40195b7 Fix Models section collapse dead pause and missing animation
The collapse handler waited a fixed itemCount*25+230ms for the
section-domino-out keyframes, but the CSS rule only targeted .list-item.
#models-section uses .models-row, so the rule matched nothing: no
animation played and itemCount was 0, leaving a flat ~230ms pause before
the section snapped shut.

- CSS: the collapse/expand animation rules now match
  :is(.list-item, .models-row) so the Models rows actually animate.
- JS: drive the collapse off the real animations via getAnimations()
  instead of a hard-coded timeout. Wait only on the section-domino-out
  keyframes (ignoring unrelated/infinite animations); collapse
  immediately when nothing animates so there is never a dead pause. A
  generation token neutralizes stale callbacks from rapid toggles, with
  a 600ms safety net so a section can't get stuck open.
2026-06-01 16:53:46 +02:00
ghreprimand
0aea1736ba Add Cookbook crash report copy action 2026-06-01 09:12:35 -05:00
pewdiepie-archdaemon
7711e14f90 Polish email reply and task controls 2026-06-01 23:02:25 +09:00
spooky
033852ab14 fix: require GGUF sources for llama downloads (#368) 2026-06-01 22:47:47 +09:00
spooky
4b72dd407b fix: report serve dependency readiness (#412) 2026-06-01 22:39:36 +09:00