Sync fork to upstream v2.0.6 (35 commits), preserve fork customizations - #5
Merged
Conversation
…mari-fixes [codex] Prepare v2.0.4 hotfixes
The auto-summary hide path added in Pasta-Devs#2821 was inadvertently reverted by f955a64 ("Local models should get the tools block"). That commit landed the tools-block feature, but its branch carried an older copy of generate.routes.ts that overwrote the auto-hide orchestration, so automatic rolling summaries stopped hiding the messages they covered. Manual summaries and unhide-on-delete were untouched and still work; only the automatic path regressed. Re-apply the dropped block onto current staging, keeping the tools-block change intact: - re-add the computeSummaryHideIds / resolveRoleplaySummaryTail imports - recompute autoHideIds (gated on hideSummarisedMessages, tail-protected) - persist hiddenMessageIds on the summary entry so deletion restores exactly the hidden set - call bulkSetHiddenFromAI and echo hiddenMessageIds in the chat_summary SSE payload The helpers, the storage method, and the manual-hide path all survived the revert, so this is a faithful restoration with no new logic. Refs Pasta-Devs#2820
…uto-summary-hide fix(summary): restore auto-hide for roleplay rolling summaries
…ose, per-chat lorebook disable, convo card info (Pasta-Devs#2847) * fix(tracker): target tracker character edits by unique id; debounce Assistant Prefill input Two roleplay-mode fixes that share no code but ship together per maintainer request. Character tracker manual edits (and removes/avatar uploads) resolved the target character with findIndex by characterId. Present-character ids come from the LLM character-tracker agent and are not guaranteed unique (the prompt allows "ID or name"), so when several present characters shared an id, findIndex always matched the first one and every edit collapsed onto the first character. Resolve the target via a unique-id match that falls back to the rendered index, mirroring the findUniqueNamedIndex rule already used by the other tracker list mutations. Apply it consistently to edit, remove, and avatar upload. The Assistant Prefill field committed every keystroke straight to a per-chat metadata mutation, so the controlled textarea only advanced after the round-trip and invalidation completed — text appeared one character at a time and got worse as chat metadata grew. Buffer the field locally and commit on blur (and on unmount, so closing the drawer mid-edit keeps the text) via a new DraftTextarea that mirrors the existing DraftNumberInput pattern. * fix(tracker): drop edits whose character id vanished; guard prefill draft re-seed while focused Addresses two regressions surfaced by adversarial review of the original fix. resolveCharacterTargetIndex fell back to the rendered index when a provided characterId was absent from live state, which could write or delete onto a different character if the tracker agent rewrote present characters between render and commit. Restore the previous drop-on-absent behavior: return -1 when the id is provided but not found, and fall back to the rendered index only for the duplicate-id (ambiguous) case. DraftTextarea re-seeded its draft from `value` on every external change, so a background metadata write (or a chat switch) could clobber keystrokes the user was mid-typing in the Assistant Prefill field. Guard the re-seed on focus, matching the sibling ThinkingTagsInput / CustomParametersInput. * fix(chat): keep the expanded prompt editor open when clicked inside a chat drawer The conversation-mode Prompt Preset "Edit Prompt" window (and the sibling Game/Scene expanded editors) is a full-screen overlay portalled to <body>, rendered above the chat settings drawer. The drawer closes itself on any capture-phase pointerdown that lands outside its panel or outside a [data-chat-floating-panel] element. Because the editor lives in a separate portal, the very first click inside it — including on the Collapse button — was treated as "outside", so the drawer closed and unmounted the editor before the edit could commit. Mouse users could neither type nor save. Mark the editor overlay with data-chat-floating-panel so the drawer's outside-click handler treats clicks inside it as inside. Closing now only happens via Collapse/Escape, which route through the editor's onClose and persist the change. * feat(lorebook): let users disable auto-activated lorebooks per chat A lorebook bound to a character (or a global / active-persona book) is force-activated in every non-game chat where it applies, with no way to turn it off from the chat UI. With several characters carrying overlapping books, entries pile up and overflow the token budget, and the only escape was to unbind the book globally. The exclusion plumbing already existed but was only wired to the Game Lorebook Keeper: filterRelevantLorebooks() and listActiveEntries() both honor an excludedLorebookIds filter, but generate/scan/dry-run/prompt paths only ever passed the game-keeper exclusions, and there was no UI or metadata field to set a per-chat exclusion. - shared: add ChatMetadata.excludedLorebookIds. - server: rename resolveGameLorebookScopeExclusions -> resolveLorebookScopeExclusions and fold per-chat excludedLorebookIds into the result for every mode, so all callers (generation, lorebook scan, dry-run, prompt preview) drop excluded books before injection. Validate the field on the metadata PATCH route. - client: surface a "disable in this chat" (eye-off) control on auto-activated books in the chat Lorebooks panel, render disabled books greyed with a re-enable control, and write excludedLorebookIds via the metadata mutation. Disabling does not unbind the book — it only suppresses it for this chat. The redundant pin-an-already-active-book control is replaced by disable; inactive books are still added via the picker. * fix(conversation): include character/persona card info when a prompt preset is selected In Conversation mode the model received only the character and user names — no description, personality, scenario, or persona info — whenever a prompt preset was selected (e.g. the default universal preset). The model would report it had no information about the character it was supposed to play. Conversation mode is deliberately excluded from the preset assembler, so in that mode a preset only supplies the conversation prompt text; it never injects character/persona card fields the way the roleplay assembler does. The identity fallback that would supply them was gated off whenever a preset was present (chatMode !== "game" && !presetId), which is correct for roleplay (the assembler covers it) but left conversation mode with nothing. Always run the identity fallback for conversation mode, regardless of preset. The injector already skips any character/persona whose profile is already present in the prompt, so custom conversation prompts that embed {{description}} et al. are not duplicated, and conversation-without-a-preset behavior is unchanged. --------- Co-authored-by: Romuromylus <233513852+Romuromylus@users.noreply.github.com>
Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
…-Devs#2854) Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
…ls (Pasta-Devs#2856) Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
…s#2858) Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
* Prepare v2.0.5 stabilization * Address v2.0.5 review feedback
…ter, author notes, drag handle, group selfie, react-gate, summary tail, streaming lag) (Pasta-Devs#2872) * fix(chat): only show swipe counter when a message has multiple swipes (Pasta-Devs#2867) * fix(chat): /continue creates a new reply when the last message is from the user (Pasta-Devs#2866) * fix(panels): show touch drag handle on touch devices regardless of width (Pasta-Devs#2869) * fix(chat): keep Author's Notes panel open when the mobile keyboard appears (Pasta-Devs#2868) * fix(chat): include lastMessageRole in conversation slash-context deps (Pasta-Devs#2866) * fix(conversation): attribute merged group-chat commands to their speaker (Pasta-Devs#2870) In Conversation-mode group chats the default merged generation produces one response containing every character's turn; all parsed commands (including [selfie]) were attributed to the first character, so a selfie taken by another character rendered the first character's appearance. Split the response by the same Name: prefixes the client uses for display and attribute each command to its segment's character, falling back to the generation's character. * fix(conversation): type merged command attribution without union narrowing (Pasta-Devs#2870) * fix(chat): gate Author's Notes focus guard to mobile to keep desktop outside-click (Pasta-Devs#2868) * fix(chat): keep toolbar overflow menu open when the mobile keyboard resizes (Pasta-Devs#2868) The overflow menu (ChatToolbarMenu) closed on every window resize while on a mobile viewport. The on-screen keyboard opening fires a resize, so opening the keyboard unmounted the menu and, with it, any open child panel rendered through it — the Author's Notes panel and the manual Summary editor. Only close the menu when the viewport width actually changes (orientation/window resize), not when the keyboard merely shrinks the height. * fix(summary): reclaim a drifted summary tail instead of stranding it (Pasta-Devs#2879) The rolling summary selected its batch with `visible.slice(-contextSize)`, and that same set bounds each entry's frozen `hiddenMessageIds` (the batch minus the protected tail). Once a previous summary's protected tail drifted beyond the last `contextSize` visible messages, no later entry ever claimed it, so it stayed visible forever and accumulated as stale context. Add `selectRollingSummaryMessages`: it keeps the plain last-`contextSize` window for the first summary and whenever the visible set fits the window, but extends the window back to the previous summary's hidden boundary when visible messages have piled up past it — pulling the drifted tail back into the batch so it is summarized and hidden on this run. Wired into the automatic summary and the manual "summarize last N" path; the explicit manual range path is unchanged. * fix(conversation): only advertise emoji reactions when commands are enabled (Pasta-Devs#2877) The `[react: emoji="…"]` capability was appended to the conversation system prompt unconditionally, but the tag is parsed, stripped, and applied inside the command pipeline that only runs when Character Commands are enabled (`characterCommands !== false`). With commands off the model was still told it could react, so it emitted the tag and the server left it in the visible message as raw text with no reaction badge. Gate the react instruction on the same `conversationCommandsEnabled` condition as its handler, so the syntax is only advertised when it will actually be processed. * fix(summary): key the summary boundary to summary-hidden ids, not the manual Hide-from-AI flag (Pasta-Devs#2879) selectRollingSummaryMessages found the previous summary's boundary by scanning for the most recent message with `hiddenFromAI` set. That flag is also written by the user's manual "Hide from AI" toggle, with no distinguishing marker, so a single manually hidden early message was misread as a summary boundary and ballooned the selection from `contextSize` to nearly the whole chat — inflating the summary-LLM input and, with `hideSummarisedMessages` enabled, mass-hiding the backlog in one run. Key the boundary scan to ids actually recorded in a live summary entry's `hiddenMessageIds` (passed via the new `summaryEntries` argument). A manual hide never appears there, so it can no longer expand the window; with no summary-hidden message before the window the plain last-`contextSize` window is used. Threaded the entries through both the automatic and manual non-range call sites. * fix(summary): anchor the boundary on messageIds too, covering pre-hiddenMessageIds entries (Pasta-Devs#2879) The boundary scan keyed only on a live entry's hiddenMessageIds. Entries auto-created before that field existed recorded messageIds and actually hid messages but have no hiddenMessageIds, so they anchored no boundary and the drifted tail was not reclaimed for those chats (no worse than baseline, but not fixed). Also include messageIds in the summary-owned set and require the boundary message to be hidden, so the protected tail (also in messageIds, but visible) is never chosen. Mirrors the hiddenMessageIds ?? messageIds fallback the unhide-on-delete path already uses. * perf(chat): throttle live stream rendering to one parse per frame (Pasta-Devs#2878) While a response streams, the live message re-rendered on every token and the markdown renderer re-parsed the entire accumulated buffer from scratch each time, so per-token work grew with the message length (~O(n²)). On fast streams — local models running on the same machine as the server, the reported worst case — that saturates the main thread and the UI stutters. Add a small requestAnimationFrame coalescer (`rafThrottle`) and a `useThrottledStreamBuffer` hook, and have the conversation and roleplay live-stream renderers subscribe through it, so the expensive markdown re-parse runs at most once per animation frame. The store's `streamBuffer` is still written on every token, so token-exact consumers such as ChatArea's auto-scroll subscriber are unaffected; only the markdown-rendering consumers throttle. The committed message is rendered from the query cache once streaming ends, so a live frame being up to ~16ms behind is never visible. On slow streams (<=60 tok/s) tokens already arrive slower than the frame budget, so this is a no-op there. * fix(chat): deliver stream-buffer resets synchronously to keep the throttled live message coherent (Pasta-Devs#2878) Throttling the live stream buffer (commit 2e1e595) re-rendered the streaming message at most once per animation frame, but `streamingCharacterId` and `thinkingBuffer` are still read un-throttled. At a group-turn boundary the generation loop does `setStreamBuffer("")` and then `setStreamingCharacterId(next)` back to back, so the speaker id flipped to the new character a frame before the throttled buffer cleared. The new speaker's row then rendered with the previous speaker's text — and in the bubble layout the streaming-preview's monotonic-growth guard (it only accepts a longer string) pinned the previous speaker's message under the new speaker for the rest of the turn, not just one frame. Throttle only ongoing growth — the buffer getting longer by appended tokens, which is the per-token re-parse cost Pasta-Devs#2878 targets. Deliver every other transition synchronously: the clear at a turn boundary, the first token after a clear, and any shrink/rewrite. Delivering the clear in the same render as the id flip restores the pre-throttle coherence, so the new speaker never inherits the previous speaker's text, while the per-token storm stays throttled. Extracted the growth check as `isOngoingStreamGrowth` for unit testing. * fix(chat): require an appended prefix for throttled stream growth (Pasta-Devs#2878) `isOngoingStreamGrowth` classified any longer buffer as ongoing growth, but the hook's `lastSeen` closure persists across active-chat switches (the conversation surface is not keyed by chat id). When two chats stream at once, switching into a chat whose live buffer is longer than the previously-viewed chat's last-seen text was misclassified as growth and held for a frame, so the new chat's stream row briefly showed the previous chat's text. A per-token stream always appends (`next === lastSeen + token`), so additionally require `next.startsWith(lastSeen)` to count as growth. A cross-chat swap (or any non-append change) fails the prefix check and is delivered synchronously, so the new chat never renders the previous chat's buffer, while genuine per-token growth is still throttled. --------- Co-authored-by: Romuromylus <233513852+Romuromylus@users.noreply.github.com>
…edge-router-output fix: preserve knowledge router prompt injections
…y-menu-clicks fix: keep memory recall modal interactive
…-sweep [codex] Prepare v2.0.6 issue sweep
Includes Gemini embedding support, Professor Mari restart ordering, impersonation prompt filtering, and the CodeRabbit-requested Gemini batch embedding follow-up.
…6-28 Fix issue sweep regressions
Brings the fork up to upstream release v2.0.6. None of the 35 new upstream commits touch the fork's five customized files, so the merge is conflict-free and all fork customizations carry over unchanged (Claude Subscription model list + Fable 5 + [1m] 1M-context handling; multi-open persistent group folders in the personas and characters panels).
✅ Bunny Review CompletedTip Review posted. The specimen has left the observation table.
|
🐰 Bunny ReviewBunny Merge Signal: ReadyTip READY
Note Mode: 🧭 Specimen Summary
🔎 Isolated DefectsTip No actionable defects isolated. 🧹 Nitpicks
✅ Control Checks
🧪 Observations
🧰 CI Status
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Keeps the fork current with Pasta-Devs upstream. After the v2.0.5 sync (#4), upstream released v2.0.6 (35 commits of stabilization). This brings the fork up to v2.0.6 while keeping every fork customization intact.
What
Merges upstream
v2.0.6(Pasta-Devs/main,49164fec) into the fork'smain(a5deecb2). The merge-base is the v2.0.5 release, and none of the 35 new commits touch the fork's five customized files, so the merge is conflict-free — the customizations carry over byte-for-byte:shared/constants/model-lists.ts) — curated[1m]list + legacy labels + Fable 5[1m]1M-context handling (server/.../claude-subscription.provider.ts)PersonasPanel.tsx,CharactersPanel.tsx,ui.store.ts)The 35 upstream commits are v2.0.6 stabilization (chat/conversation/summary fixes, Professor Mari streaming, memory-recall fixes, issue-sweep UI, theme accent pulse, settings polish).
The merge commit preserves both parents (
a5deecb2fork main +49164fecupstream v2.0.6).pnpm check(typecheck + lint + build) passes.Note for updating installs
v2.0.6 adds no new dependencies, so updating an install to this revision is a
git pull+ rebuild.