* fix: include in-memory templates in group participant character list
_getCharacterList() only fetched user templates from the /api/presets/templates
endpoint. When a character was just created in the Character tab, the async
auto-save to the templates API might not have completed by the time the Group
tab loaded its participant dropdown — causing newly created characters to be
missing.
Now also merges the in-memory userTemplates array from presets.js as a
fallback. These are updated as soon as the async save completes (via the
loadUserTemplates callback), so they bridge the gap between character creation
and API persistence.
Fixes#3207
* fix: optimistic userTemplates update on character save
Update the in-memory userTemplates array immediately when saveCustomPreset()
succeeds, before the fire-and-forget templates API POST completes. This
bridges the timing gap where _getCharacterList() calls getUserTemplates()
and gets stale data because loadUserTemplates() hasn't been triggered yet.
* test: verify group participant dropdown merges in-memory templates
Source-level guards for the #3207 fix:
- group.js imports and calls getUserTemplates() to merge in-memory templates
- presets.js exports getUserTemplates and does optimistic in-memory update on save
5 tests ensuring the fix can't be silently reverted.
* fix: generate client-side id for optimistic update, return shallow copy from getUserTemplates
1. New characters now get a 'user-<hex>' id immediately on save, matching
the server's convention (uuid.uuid4().hex[:8]). Previously the id was ''
which the merge guard in _getCharacterList filtered as falsy.
2. getUserTemplates() now returns [...userTemplates] so callers cannot
accidentally mutate module state.
* fix(group.js): fix selection drop-downs behavior
- add an identifier to the selection drop-downs
based on what type it is.
- fix behavior of continuously adding a row
when a user clicks the "Group" tab button.
- fix behavior of not repopulating existing
selection drop-downs whenever a user
clicks the "Group" tab button.
* fix(#3207): remove duplicate of latest persona
- fix the duplication of the latest persona
or character being shown in selection
drop-downs.
- remove unnecessary blocks of code in
`_getCharacterList()`
- add functionality to show error toast if saving
a preset template/character fails.
- add functionality to revert optimistic update
of preset template/character if saving fails.
* chore(group.js,preset.js): fix test & format errors
remove trailing whitespaces in lines 230 and 232
in /static/group.js
add back the expected syntax from
tests/test_group_character_dropdown.py
* fix(presets.js,group.js): fix runtime errors
as stated in a comment by @alteixeira20,
runtime errors exist for the applied fixes.
fixes:
- missing ending `]`
querySelectorAll("select.preset-input[data-selection-type=character")
in `group.js`
- spelling error in `modelSelection.vale` in `group.js`
- fix the ordering logic error in optimistic rollback where `Object.assign` is called first before the clone happens in `saveCustomPreset` in `presets.js`.
- add tests for the cloning logic bug with the same format as previous tests by checking the order of LOC in `tests/test_group_character_dropdown.py`.
---------
Co-authored-by: michaelxer <michaelxer@users.noreply.github.com>
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
_getCharacterList() had two bugs that silently dropped every
user-created persona from the group participant picker:
1. The /api/presets/templates endpoint returns a JSON array directly,
but the code read `data.templates` (always undefined). The forEach
over `data.templates || []` iterated over an empty array every time,
so no user templates were ever added.
2. Even if the array had been read correctly, the `t.isCharacter` guard
would have filtered them all out — user templates are saved by
presets.js without that flag, which is only present on built-in
PROMPT_TEMPLATES entries.
Fix: accept both the direct-array and the {templates:[]} shapes, drop
the isCharacter guard (user_templates are personas by definition), and
use the correct field name (system_prompt, not prompt) so the character
prompt actually reaches the group chat.
Fixes#1656
Both _getModels() and getAllModels() store the sorted copy in a cache
variable but return the original unsorted array on first invocation.
Subsequent calls return the cache (sorted), causing inconsistent
model picker ordering on first render.
Hoist the HTML-escape lookup table in static/js/ui.js out of the
String.replace callback so it is allocated once instead of on every
matched character. esc() is the canonical escaper aliased across 27
modules and runs on essentially every render, so this removes a lot of
short-lived garbage on the hottest text path. Output is byte-identical
(verified across null/undefined/emoji/attribute edge cases).
Also build the <select> option lists in cookbook-hwfit.js and group.js
by accumulating a string and assigning innerHTML once, instead of
`innerHTML +=` inside a forEach (which makes the browser re-parse the
element's markup on every iteration). Final DOM is unchanged.
Pure micro-optimizations; no behavior change.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>