Commit Graph

478 Commits

Author SHA1 Message Date
pewdiepie-archdaemon 25c2e6c4a6 Cookbook GPU/RAM toggle: default to whichever pool has more capacity
On initial render, compare total_ram_gb vs gpu_vram_gb — if RAM is
the larger pool, pre-select the RAM (count=0) button instead of the
max-GPU button. Boxes with more system RAM than VRAM (low-VRAM
GPU + lots of system memory, or CPU-only servers with a small
adapter) now open on the dominant pool.
2026-06-13 21:40:40 +09:00
pewdiepie-archdaemon 32c02a9d86 Cookbook toolbar: move Search next to Standard, Engine/Quant/Context to right
New order: [Standard ▾] [Search ............] [Engine] [Quant] [Context]
so the two primary picks (type + free text) sit together at the
left, with the more advanced filters lined up to the right.
2026-06-13 21:30:05 +09:00
pewdiepie-archdaemon 23e1489f41 Cookbook fold: smooth max-height + opacity transition
display:none toggle was instant and felt jarring during auto-fold/
auto-expand. Swapped to a CSS class `.is-folded` that transitions
max-height (0 ↔ 1200px) and opacity (0 ↔ 1) over ~280ms with ease,
so both manual chevron clicks and the scroll-driven toggles slide
in/out smoothly.
2026-06-13 20:14:34 +09:00
pewdiepie-archdaemon 260f0aeef8 Cookbook auto-fold: auto-expand when scrolling back to top
scroll handler now tracks per-target scrollTop via WeakMap. Downward
scroll on any scroller in the cookbook modal folds Direct Download;
scrolling back to top (scrollTop <= 0) unfolds it. Manual chevron
clicks still win — they persist to localStorage; auto-toggles
don't, so the user's last explicit pick survives reload.
2026-06-13 20:12:30 +09:00
pewdiepie-archdaemon b9ede54ca0 Cookbook auto-fold: capture-phase scroll listener catches hwfit-list
IntersectionObserver missed the case because scrolling inside the
nested .hwfit-list (max-height:52vh own scroller) doesn't move the
header out of view at all. The user wants any downward scroll in
the scan/download area to fold Direct Download.

Switched to a capture-phase scroll listener on #cookbook-modal that
catches every scroll event from any nested scroller (.hwfit-list,
.cookbook-body, .modal-content). Folds only on downward scrolls so
scrolling back up doesn't keep re-folding.
2026-06-13 20:10:46 +09:00
pewdiepie-archdaemon 8ad239d9de Cookbook auto-fold: use IntersectionObserver to catch any scroll source
The scroll listener on .cookbook-body never fired — the user is
likely scrolling inside the nested .hwfit-list (max-height:52vh)
which doesn't bubble to its parent. IntersectionObserver fires
whenever the Direct Download header crosses the viewport edge
regardless of which container moved.

Folds only when boundingClientRect.top < 0 (header pushed up past
the top) so modal close / detach doesn't trigger it.
2026-06-13 20:07:32 +09:00
pewdiepie-archdaemon eb6a339cd6 Cookbook auto-fold: target the actual scroll container (.cookbook-body)
Previous .modal-body / .cookbook-content lookup matched neither the
desktop scroller (.cookbook-body) nor the mobile one (#cookbook-modal
.modal-content), so the scroll listener was attached to document.body
and never fired. Walk up to whichever scroller actually exists.
2026-06-13 20:05:33 +09:00
pewdiepie-archdaemon 2fb764e779 Cookbook: auto-fold Direct Download when its header scrolls past top
Added a scroll listener on the parent .modal-body / cookbook-content
that folds the Direct Download body once its h2 header has scrolled
above the container's top edge. Frees the viewport for the Scan
section below while leaving the chevron clickable to expand again.

Auto-fold doesn't write to localStorage (only manual clicks do)
so the user's last explicit preference still wins on reload.
2026-06-13 20:03:14 +09:00
pewdiepie-archdaemon 38f942d443 Cookbook Trending: drop ↻ refresh button (trending list reloads on toggle) 2026-06-13 20:01:54 +09:00
pewdiepie-archdaemon f5be846297 Cookbook Trending: shrink trending-up icon 18px → 15px 2026-06-13 20:01:26 +09:00
pewdiepie-archdaemon b3014aebde Cookbook Trending: accent trending-up icon + chevron on right + larger row
- Added a trending-up (market-up) SVG before the label, tinted
  accent so the section reads as "what's hot".
- Chevron ▸ moved from the left to the right side of the toggle
  row (still rotates via the existing CSS).
- Bumped the toggle row taller (26→34px) with 13px font + 18px
  icon so the section header has more presence.
2026-06-13 19:59:41 +09:00
pewdiepie-archdaemon 4c3618f630 Brain cards 32px tall + Trending tab up 8px + drop hwfit Rescan
- Brain admin-card header rows get min-height:32px so cards with
  toggles and cards without (Inject Skills) align.
- Cookbook Trending models tab nudged up 8px (top:-3 → -11).
- Removed the ↻ RESCAN button in hwfit toolbar; manual EDIT still
  available and auto-probe runs on container restart.
2026-06-13 19:56:22 +09:00
pewdiepie-archdaemon 7dae40e820 Skills: Audit on left + accent star, Select w/ dot/X icon swap
- Reordered the toolbar so Audit sits left of Select (matches the
  brain memories layout where bulk actions live before Select)
- Renamed "Audit all" → "Audit"
- Star icon in Audit now tinted with var(--accent, var(--red))
- Select button gets the same dot/X SVG swap used in brain
  memories (dot in idle state, X when bulk-select mode is active)
2026-06-13 15:47:16 +09:00
pewdiepie-archdaemon e4b37542a5 Revert Chat/Agent mode tag in message header
Per user report — the tag's mode metadata coincided with a
500 error on agent mode (especially on mobile). Removing the
UI tag, the chat.js writes of metadata.mode, and the CSS pill
so agent mode posts work cleanly again.

Touches:
- chat.js: drop _sendMode capture + meta.mode writes (user + assistant)
- chatRenderer.js: roleTimestamp() back to a single (when) arg, drop
  the .role-mode-tag append; updated three call sites
- style.css: dropped .role-mode-tag and .role-mode-agent rules
2026-06-13 11:39:32 +09:00
pewdiepie-archdaemon c621542c9d Doc compose: accent prefix labels for each field + Cc btn up 2px
- Each input now has a sibling .email-field-prefix span (To / Cc /
  Bcc / Subject) absolute-positioned at the left edge in the
  accent color. Inputs get padding-left:44px (64px for Subject)
  so typed text doesn't slide under the prefix.
- Placeholders shrink back to just the example so only the
  prefix gets the accent color, not the example text.
- Cc toggle moved another 2px up (calc(50% + 4px) → calc(50% + 2px)).
2026-06-13 09:35:21 +09:00
pewdiepie-archdaemon 45c856542d Doc compose: drop field labels, expand placeholders with examples
- Removed the <label>To/Cc/Bcc/Subject</label> elements — they
  doubled what the placeholder said.
- Placeholders now carry both the field name AND an example so an
  empty input still tells the user what to type:
    To  recipient@example.com
    Cc  cc@example.com, example2
    Bcc  bcc@example.com
    Subject
2026-06-13 09:29:20 +09:00
pewdiepie-archdaemon 33a3fcb0f1 Doc compose: X close button inside Cc and Bcc fields
Adds a per-field X (24x24 SVG, opacity 0.4 → 1 + accent on hover)
absolute-positioned at the right edge of each Cc/Bcc field. Click
hides both rows, clears their inputs, and restores the Cc opener
on the To row. Inputs get padding-right:32px so the close button
doesn't overlap typed text.
2026-06-13 08:40:37 +09:00
pewdiepie-archdaemon df697066a4 Email reminders: "Note" picks open a write-your-own-text modal
- Renamed "Note (no timer)" → "Note".
- Clicking it now opens a small modal with a textarea + Save/Cancel.
- The typed text becomes the todo item; due_date is omitted so no
  timer fires. Esc cancels; Cmd/Ctrl+Enter saves.
2026-06-13 08:09:37 +09:00
pewdiepie-archdaemon 6432ebf047 Email reminders: add "Note (no timer)" option
Re-adds the timer-less note path next to the time-based presets.
Picking it POSTs the same payload but omits due_date so the entry
lives in notes as a plain reply todo with no reminder firing.
Toast: "Reply note saved" instead of "Todo reminder set for …".
2026-06-13 08:06:53 +09:00
pewdiepie-archdaemon c06bfe990f Email library: reset select-mode + selectedUids on open
Was sticking on toggled-on state if the user closed the library
while in select-mode — reopening showed the Cancel/X toggle even
though no emails were selected. Force-reset state._selectMode and
state._selectedUids in openEmailLibrary so each open starts fresh.
2026-06-13 08:03:10 +09:00
pewdiepie-archdaemon 8a8c13289f AI reply: 1st click shows cached, 2nd click clears + opens menu
Correct behavior:
1. Cached draft + first click → opens the cached reply
2. Cached draft + second click → clears the cache and opens the
   Fast/Full + context menu so the user can request a fresh draft
3. No cache → opens the menu directly

Per-button shownOnce dataset tracks the first-click state so the
second click triggers the menu instead of replaying the cached
reply again.
2026-06-13 08:01:27 +09:00
pewdiepie-archdaemon 4dd58ee267 AI reply always reopens menu + Cc toggle 2px down in doc compose
- AI reply: removed the cached_ai_reply shortcut so clicking the
  button always reopens the Fast/Full + context menu. Lets the user
  ask for a fresh draft (with new steering) instead of being locked
  into the cached one.
- .email-cc-toggle gets position:relative + top:2px so it
  baseline-aligns with the To: field chips next to it in the
  document email compose.
2026-06-13 07:59:41 +09:00
pewdiepie-archdaemon 05f0b4d7a1 Email library: Select button matches brain memories (dot↔X swap)
- Initial button: dot-in-circle SVG + "Select" label
- After click (select-mode on): X SVG + "Cancel" label + .active class
- Same SVG glyphs as memory.js so the two pages feel consistent.
Hooked into the toolbar Select toggle AND the bulk-bar Cancel button
so both reset the icon state.
2026-06-13 07:58:24 +09:00
pewdiepie-archdaemon a0e341dd9a AI reply menu: viewport-aware placement on mobile
- Horizontal: max-width and left already clamped to viewport-16.
- Vertical: prefer below the button, but flip ABOVE if there's
  more space there (e.g. button near the bottom of the viewport).
- max-height clamped to viewport-16 with overflow:auto as a final
  guard so the menu can never extend past the screen edge.
2026-06-13 07:21:18 +09:00
pewdiepie-archdaemon 8ec9d78554 AI reply menu: Fast/Full sit below the context textarea as confirm
Dropped the two-step (pick mode → context → OK) flow. Now the
context textarea is at the top of the popover and Fast (left) /
Full (right) sit below as the confirm buttons themselves — they
fire the draft with whatever's currently in the textarea (empty
= no steering).
2026-06-13 07:16:26 +09:00
pewdiepie-archdaemon 0cc51b2e5d AI reply menu: outside-click closer ignores clicks inside the menu
The document-level capture listener was closing the popover on
ANY click — including clicks inside the context textarea, which
made it impossible to focus the input. Replaced with an inline
handler that bails when the click target is inside the menu.
2026-06-13 07:15:44 +09:00
pewdiepie-archdaemon 2e68b2ec36 AI reply menu: click Fast/Full → context input → OK
Restructured flow:
1. Click Fast or Full → reveals an optional context textarea
   ("Add context (optional)") below
2. Type optional steering note or leave blank
3. Click OK → triggers the draft with the chosen mode + note

Dropped the standalone … note-toggle button — the textarea is now
gated on picking a mode, which makes it easier to discover.
2026-06-13 06:59:10 +09:00
pewdiepie-archdaemon 615602204d AI reply menu: drop draft sub-buttons + viewport-clamp on mobile
- Removed the conditional Draft fast / Draft full buttons. Note
  textarea is always-on via the … toggle, and whatever's in it
  is picked up by the existing Fast / Full buttons as noteHint.
- Clamped the popover max-width and left position to
  Math.min(220, viewport-16) + 8px margin so the (now wider) menu
  doesn't spill off the right edge on narrow mobile screens.
2026-06-12 23:41:46 +09:00
pewdiepie-archdaemon 52a9a18364 AI reply menu: add … note input to steer the draft
Top row keeps Fast / Full + a new horizontal-dots button. Clicking
the dots reveals a textarea ("e.g. reply nicely but say no"); as
soon as text is in it the panel shows Draft fast / Draft full
buttons that pass the note through as noteHint to the AI reply
endpoint. Empty textarea hides the draft buttons so the user only
gets the steered draft when they've actually typed direction.
2026-06-12 23:39:05 +09:00
muhamed hamed 1fb5e4f1d7 fix: detect HuggingFace token when downloading cookbook models (#3459)
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
2026-06-11 21:53:16 +01:00
Michael 998211c26f fix: read allow_bash/allow_web_search from JSON body (#3229) (#3281)
* fix: read allow_bash/allow_web_search from JSON body (#3229)

API callers using Content-Type: application/json had bash and web
tools silently disabled because allow_bash / allow_web_search were
only read from FormData (which is empty for JSON requests).

Changes:
- Fall back to JSON body for allow_bash and allow_web_search values
- Only add bash/web_search to disabled_tools when explicitly set to a
  falsy value; when unset (None), defer to per-user privilege checks
- Admins with can_use_bash=True now get bash enabled by default

Fixes #3229

* fix: always send explicit allow_bash/allow_web_search from frontend

The backend 'is not None' guard (from prior commit) is correct for API
callers, but the frontend only sent allow_bash=true when the toggle was
ON — omission meant 'unspecified' which the backend treated as 'don't
disable'. Now the frontend always sends an explicit true/false value:

- allow_bash: sent on every request (checked ? 'true' : 'false')
- allow_web_search: explicit 'false' when toggle is off in agent mode

With explicit frontend values, the 'is not None' guard is safe:
- explicit true → tool enabled
- explicit false → tool disabled
- None (API caller omission) → defer to per-user privilege

---------

Co-authored-by: michaelxer <michaelxer@users.noreply.github.com>
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
2026-06-11 19:14:41 +01:00
Carles Siles fdd63819a8 fix: expand cookbook error output tail from 12 to 50 lines (#1538)
* fix: expand cookbook error output tail from 12 to 50 lines

When a task reaches status 'error', the status endpoint was returning
only the last 12 lines of the subprocess log. The existing context-menu
'Copy last 50 lines' action was therefore copying the same 12 lines,
making it useless for diagnosing failures that produce long stack traces
or build output.

- Set _tail_lines = 50 when status == 'error', keep 12 for running tasks
- Initialise exit_code = None before the status-classification block so
  it is always defined in the result dict (was only set inside the
  is_alive branch, potential NameError in the dead-session path)
- Include exit_code in the task-status response dict
- JS poller captures exit_code from live data into local task state

The frontend output panel and 'Copy last 50 lines' now show the actual
error context without any UI changes.

* refactor: extract output-tail logic to testable helper + behavioral tests

Addresses review feedback on #1538: the previous tests were source-level
string guards. Extract the tail-slicing into a dependency-free helper
(routes/cookbook_output.error_aware_output_tail) and replace the guards
with behavioral tests that exercise the actual logic:

- error status with a 200-line snapshot -> exactly the last 50 lines
- running/ready/completed/stopped/unknown -> last 12 lines
- short snapshot -> all lines, no padding
- empty snapshot -> empty string
- error tail is a strict superset (suffix-compatible) of the non-error tail

The helper has no FastAPI/SQLAlchemy imports so it unit-tests without
standing up the app.

---------

Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
2026-06-11 17:55:33 +01:00
Kenny Van de Maele 0946f7b216 feat(agent): confine agent file/shell tools to a selectable workspace (#3665)
* feat(agent): workspace confinement via context-local binding + get_workspace tool

Bind the per-turn workspace once in execute_tool_block; the shared path
resolvers (_resolve_tool_path / _resolve_search_root) and the subprocess cwd
helper (agent_cwd) read it, so file tools + bash/python are confined centrally
and a new tool that uses the shared helpers cannot accidentally bypass it.

Adds the admin-gated /api/workspace/browse picker, a workspace pill + directory
modal (reusing existing modal/button CSS), the /workspace slash command, and a
get_workspace tool (replaces a system-prompt block). Confinement is OS-agnostic
(realpath/normcase/commonpath) and docker-safe (container paths, no host
assumptions). Reopens #2023.

* ux(workspace): clarify workspace is not a sandbox

Picker modal note + pill tooltip + get_workspace tool/output wording now state
plainly: read_file/write_file/edit_file/grep/glob/ls are confined to the folder,
but bash/python only start there (cwd) and are not sandboxed. Modal note reuses
the existing .muted class.

* fix(agent): treat an active workspace as file-work intent

A vague low-signal message (e.g. "look at the local project") matches no
domain keywords, so tool retrieval is skipped and only always-available tools
are offered — leaving the agent with no file access even though a workspace is
set. When a workspace is active, include the file/code tools (incl.
get_workspace) on low-signal turns so the agent can act on the folder.

Also requires the tool index (ChromaDB) to be reachable for normal retrieval;
that is an environment dependency, not part of this change.

* ux(workspace): hide pill + overflow entry in chat mode

Workspace only scopes the agent's file/shell tools, so the pill and the
overflow 'Workspace' entry are agent-only now — hidden in chat mode like the
bash toggle. Mode read from the DOM in syncWorkspaceIndicator; applyMode() is
called from the agent/chat setMode handler.

* prompt(tools): steer bash/python to defer to the dedicated file tools

bash/python schema descriptions (what native-tool-calling models read) were
bare and gave no steer, so models would do file ops via the shell (e.g. writing
SVG/HTML, which then dumps raw markup into the tool preview). Tell bash/python
in the schema + tool-index + prompt section to prefer read_file/write_file/
edit_file/grep/glob/ls and only be used for what those do not cover.

* prompt(tools): keep bash/python deferral generic (no hardcoded tool names)

Reference 'a dedicated tool' rather than listing read_file/write_file/grep/etc.
by name, so the guidance does not go stale if those tools are renamed.

* style(workspace): drop em-dashes from added code comments/strings

* ux(workspace): terser non-sandbox note in picker (no tool-name list)

* ux(workspace): mirror terse non-sandbox wording in pill tooltip

* chore: untrack local venv symlink (run-only, not part of the feature)

* prompt(workspace): keep get_workspace text generic (no hardcoded tool names)

* fix(agent): low-signal + workspace surfaces only read-only file tools

Intersect the files tool group with PLAN_MODE_READONLY_TOOLS so a vague message
in a workspace exposes read_file/grep/glob/ls/get_workspace for exploration, but
not write_file/edit_file/bash/python -- those wait for a request that actually
calls for them (RAG retrieval still adds them on a real ask).

* feat(workspace): cap browse listing at 500 dirs with a truncated hint

Mirror the filesystem_tools._CODENAV_MAX_HITS pattern with a module-local
_MAX_BROWSE_DIRS so a directory with thousands of children does not dump every
row into the picker; the response carries a truncated flag and the modal tells
the user to type a path to jump in.

* chore: untrack local venv symlink (run-only artifact)

* fix(workspace): vet the workspace root against the sensitive-path deny list at bind time

The in-workspace resolver deny-lists sensitive paths inside the workspace,
but the empty-path search root is the workspace itself, so a workspace of
~/.ssh could be listed via ls with no path. vet_workspace() (public, in
tool_execution next to the resolvers) rejects non-directories and sensitive
roots before the path is ever bound; chat_routes uses it instead of its
inline isdir check.

* fix(workspace): reject filesystem roots and stop showing rejected workspaces as active

Review findings from #3665:

P2: vet_workspace accepted / (and would accept drive/UNC roots), which makes
every absolute path 'inside' the workspace and collapses confinement into
host-wide file access. A root is its own dirname, so reject when
dirname(resolved) == resolved; the browse response now carries a selectable
flag and the picker disables 'Use this folder' on unselectable dirs.

P3: /workspace set stored any string client-side and the chat route silently
dropped rejected values, so the pill could claim a confinement that was not
in effect. New admin-gated /api/workspace/vet validates manual paths before
they persist (canonical path returned), and when a posted workspace is
rejected at send time the stream emits workspace_rejected so the client
clears the stored value and toasts instead of continuing silently.

* fix(workspace): check caller privilege before vetting the posted workspace

Review finding: /api/chat_stream called vet_workspace() on the posted value
for every caller and emitted workspace_rejected on failure, so a non-admin
who can chat but cannot use file/shell tools could distinguish existing
directories from missing/file/sensitive/root paths by whether the event
appeared. The resolution now lives in _resolve_request_workspace, which
drops the submitted value uniformly for non-admin callers, with no vetting
and no event, before the path ever touches the filesystem. Admin and
single-user behavior is unchanged. Test pins that valid and invalid paths
are indistinguishable for a non-admin and that vet_workspace is never
invoked for them.
2026-06-11 18:17:54 +02:00
pewdiepie-archdaemon 9bca81cc07 Email reader: don't search-pivot from From/To/Cc chips + accent search icon
- Window-level recipient-chip click handler now bails if the chip
  is inside .email-reader-meta — the per-reader handler still
  toggles the expanded-address view on click.
- The from-sender (magnifying glass) search button SVG is now
  tinted with var(--accent-primary) so it stands out as a deliberate
  search action against the neutral Reply / Forward / etc icons.
2026-06-11 22:05:40 +09:00
pewdiepie-archdaemon c94c973808 Email reader docked: action cluster drops below To/Cc when expanded
Moved the action cluster out of the From row to a sibling of meta
inside .email-reader-meta. Undocked: cluster is absolute-positioned
top-right of the header so it overlays the From line as before.
Docked: cluster is in-flow at the bottom of the meta column, so it
sits below the From row when collapsed and below the To/Cc rows
when the user expands the recipient details via the chevron.
2026-06-11 22:04:34 +09:00
pewdiepie-archdaemon 793ad88fe1 Chat: fix mode-tag breakage — toggleState wasn't in scope at those sites
The previous commit read toggleState.mode before it was declared
(send-time site near line 632) and outside its closure (assistant
finalize site near line 3426). Both threw ReferenceError / TDZ on
first send, which crashed the chat send + render pipeline.

Read fresh via Storage.loadToggleState() at each site, defaulting to
'chat' on any error. Mode-tag rendering otherwise unchanged.
2026-06-11 22:00:22 +09:00
Rohithmatham12 16ae0849bb fix: quote kernels repair package spec 2026-06-11 14:56:35 +03:00
pewdiepie-archdaemon e46a86a5af Chat: show Chat/Agent tag next to message timestamp
Sometimes the user lands in chat mode without realizing — surface the
mode the message went out on as a small uppercase pill right after the
timestamp in the role header.

- roleTimestamp(when, mode) gains an optional mode arg. Agent renders
  in accent; Chat renders in muted/neutral. Other values render
  nothing (back-compat for older history without the field).
- The three roleTimestamp call sites pass metadata?.mode through.
- chat.js writes mode into the user-message metadata at send time and
  into the assistant metadata when the active-stream render lands,
  reading toggleState.mode so research/agent overrides upstream still
  flow through correctly.

Historical messages from before this change just don't show the pill —
graceful fallback, no migration needed.
2026-06-11 20:44:18 +09:00
pewdiepie-archdaemon 3fa872dac7 Email library: match magnifier color/opacity to other search fields
opacity 0.55 → 0.45 and explicit color:var(--fg), matching the
.cal-search-icon treatment so the email chip-bar magnifier reads at
the same muted intensity as the calendar search field.
2026-06-11 20:33:16 +09:00
pewdiepie-archdaemon f04d87756f Email reader: search input up 1px, AI reply menu pared to Fast/Full
Search input gets position:relative;top:-1px so the placeholder text
sits 1px higher inside the chip bar.

AI reply choice popover: drop the '...' kebab and the 'Draft with
note' textarea row entirely. Replace the concentric-circle Full icon
with our standard accent dot (filled 6px circle in viewBox 24).
2026-06-11 20:31:04 +09:00
pewdiepie-archdaemon e078cc1cdf Revert "Email reader: AI reply becomes a split button (main + caret)"
This reverts commit 04de1d4dd6.
2026-06-11 20:28:42 +09:00
pewdiepie-archdaemon 04de1d4dd6 Email reader: AI reply becomes a split button (main + caret)
Main button: open cached AI draft if one exists, otherwise generate
a fast draft inline. No more intermediate Fast/Full/Note menu.

Caret on the side opens a focused popover with just a textarea +
Generate button — the user types instructions (e.g. 'thank them and
confirm Tuesday at 2', 'decline politely') and submitting fires the
full-mode generation with those instructions as the noteHint.

- _aiReplySplitButtonHtml(data) centralizes the new HTML so all three
  reader render sites use the same markup.
- _showAiReplyChoice rewritten — drops the Fast/Full toggle row plus
  the kebab + 'Draft with note' two-step. Ctrl/Cmd+Enter submits.
- _handleAiReplyButton routes based on which inner button was clicked
  (caret → popover, main → run-or-open).
- The three reader event registrations now listen on .ai-reply-split
  so both inner buttons feed the same handler.
2026-06-11 20:24:19 +09:00
pewdiepie-archdaemon c2e0e5f02a Email reader: all actions on the From row, wrap when narrow
Found the culprit — the docked-modal CSS forced .email-reader-meta-row
into a single-column grid, which collapsed the From row into a
vertical stack and pushed the action buttons below it.

Fix:
- Merged the primary + secondary action rows into one flat
  .email-reader-actions-inline cluster inside the From row
- Made the cluster flex-wrap so it stays inline when undocked and
  wraps below the chip when truly cramped (docked, narrow tab)
- Excluded .email-reader-meta-from from the docked-modal and
  narrow-docpane grid-stack rules — those overrides now only
  apply to the To/Cc detail rows
2026-06-11 20:07:35 +09:00
pewdiepie-archdaemon c8654f3166 Email library: magnifying glass inside the chip-bar search field
Absolutely-positioned 13px search SVG at the left edge of the chip bar
(same circle+line glyph used elsewhere). Bar padding-left bumped 8→26
to leave room. pointer-events:none on the icon so clicks still land
on the input, opacity 0.55 to match other muted prefix icons.
2026-06-11 20:06:22 +09:00
pewdiepie-archdaemon abb1667a82 Email: bookmark icon everywhere for favorites; subject matches in suggestions
Star → bookmark banner SVG also in the card title row (em.is_flagged
glyph) and the inbox toolbar's _starIcon / _starFilledIcon, so every
favorites affordance matches the chats sidebar bookmark.

Search dropdown gains a third suggestion kind:
- kind: 'email' rows surface emails from the snapshot whose subject or
  sender name match the typed term (top 4, scored by startsWith vs
  substring). Render row carries a small envelope glyph + bolded
  subject + 'from name' on the right.
- Picking one closes the dropdown and expands that exact card via
  _toggleCardPreview, scrolling it into view.
2026-06-11 19:46:45 +09:00
pewdiepie-archdaemon b11354f45e Email reader: primary action row literally inside the From row
Restructured the DOM so the Reply / Reply-all / Forward row lives
INSIDE the email-reader-meta-from div (after the chips span), and
the Summary / AI / More row sits directly below as a sibling of
From inside the meta. Killed the outer email-reader-actions
wrapper that kept letting the buttons drift out of position.

CSS now pushes the primary row right via margin-left:auto on the
From row and right-aligns the secondary row below it.
2026-06-11 19:46:06 +09:00
pewdiepie-archdaemon 2a93895b8a Email library: swap Favorites icon star→bookmark banner (matches chat .session-fav) 2026-06-11 19:39:12 +09:00
pewdiepie-archdaemon 8925b3bf77 Email library: filter pills render as icon-only chips
After picking a filter from the dropdown the pill was 'icon + Unread'.
Drop the text — the icon is the affordance — so the pill collapses to
just the glyph + ×. Hover surfaces the friendly label via the title
attribute. Contact + text pills still carry their text label.
2026-06-11 19:37:56 +09:00
pewdiepie-archdaemon dd977d03e2 Email library chip-bar: filter + tag suggestions with their icons
Typing a filter keyword now surfaces the matching filter row in the
autocomplete (each with its existing dropdown icon). Picking one pins
a filter pill and drives the global filter state.

Keyword catalog (_LIB_FILTER_OPTIONS):
- has-attachments  ← 'attachment', 'attachments', 'has attachment', 'attach'
- unread           ← 'unread', 'new', 'unseen'
- favorites        ← 'favorite', 'starred', 'star', 'flagged'
- undone           ← 'undone', 'pending', 'todo'
- reminders        ← 'reminder', 'reminders'
- unanswered      ← 'unanswered', 'unreplied', 'no reply'
- pending_30d      ← 'pending 30d', 'pending', 'recent pending'
- stale_30d        ← 'stale', 'old', 'stale 30d'
- tag:urgent       ← 'urgent', 'critical'
- tag:reply-soon   ← 'reply soon', 'reply', 'follow up'
- tag:spam         ← 'spam', 'junk'
- tag:newsletter   ← 'newsletter', 'newsletters', 'subscriptions'
- tag:marketing    ← 'marketing', 'promo', 'promotional'

Filter pill behaviour:
- Only one filter pill is active at a time — adding a new one replaces
  any existing filter pill.
- _applyFilterPillSideEffect drives the existing #email-lib-filter
  select (or the #email-attach-btn toggle for has-attachments). The
  server-side list refetch follows for free via the existing 'change'
  handler.
- Removing the filter pill clears the side effect.

Pill render gains the filter icon as a leading glyph; the suggestion
row renders icon + label in the accent colour so it visually reads as
a filter, not a contact.
2026-06-11 19:33:55 +09:00
pewdiepie-archdaemon 753d6d8bb9 Email reader: actions inline on the From row
With the meta collapsed to a single visible From row + chevron,
there is room to put the action cluster on that same row as a
right-aligned sibling. Dropped the absolute positioning and
gradient-fade overlap — actions now flex-end via margin-left:auto
so From sits on the left and Reply / Reply-all / Forward / AI /
Summary / More all sit on the right of the same row.

Also moved the chevron inside the recipient-chips span so it sits
adjacent to the sender chip instead of wrapping onto a second line.
2026-06-11 19:23:34 +09:00