feat(strategies): follow the core's strategy order, and keep its folders - #445
Merged
Conversation
Two halves of the same story, both of them things moonproto only started synchronizing in 2e67562 and this terminal had no way to represent before. Strategy order. A core's strategy list is an arrangement the operator made in MoonBot, and the library now carries it as the row sequence of a Full snapshot. The tree used to throw it away, sorting folders into a byte-wise alphabet that put Zeta above alpha and every Cyrillic name after every Latin one. A folder now sits where its first strategy sits in the core's own list, taken from the unfiltered list so that typing in the search box cannot rearrange the tree. The operator can also change that order: two footer buttons, Ctrl+Shift+Up/Down, and two context-menu items move the selection inside its folder, and the result goes to the core as a complete id sequence. Alt+Up/Down was avoided on purpose — it ships as "shift the sell order's price", and one chord with two meanings is a trap. Until the core echoes the new order back, the tree draws the sequence that was sent; without that a second press would be computed from the arrangement the first one had already replaced. The same sequence is applied to every other outgoing strategy sync, or a checkbox toggled in the meantime would hand the core back the order the operator had just replaced. Folders. The core keeps a versioned folder tree now, empty folders included, so "new folder" is a real edit rather than a mark that lives until the window closes. Edits travel as intents — add this folder, move that subtree — and the complete desired tree is assembled on the feed thread, where the newest tree is known: the wire form deletes every folder the list omits, so a tree assembled from a window's snapshot turns a create into a silent delete of whatever arrived meanwhile. A rename sends the rewritten tree with the rows, or the emptied old path stays behind as a folder of its own. Two capability flags, not one. `supported` means the core keeps a tree and its empty folders can be drawn; `editable` means every path in that tree would also survive moonproto's validator. They differ on a real account: MoonBot allows a `/` inside a folder name, moonproto's own state then reports the split halves as parent folders, and its validator refuses those — so on such a core every folder edit would be refused whole. The terminal asks once and keeps its local marks instead of failing per command. An empty folder draws neither a caret that opens onto nothing nor a checkbox that covers no strategy, and its tooltip says whether the core keeps it. Known gaps, deliberate: a refused reorder or folder edit reaches only the log, so the arrangement on screen reverts without saying why; the analytics purge still deletes an emptied folder the legacy way; empty folders are hidden while a search is running; their order among themselves is by name, which the protocol leaves unsynchronized and MoonBot orders differently.
Both handlers — the Rust panic hook and the native SEH filter — captured a backtrace first and wrote the record afterwards. Symbolizing goes through dbghelp, which is not thread-safe and cannot run on an exhausted stack, so when the capture itself faulted the process died inside the handler with nothing written at all: an empty panic.log, an empty app log, and a Windows crash record naming dbghelp.dll as the faulting module rather than anything of ours. That is not a hypothetical. A startup stack overflow presented exactly this way and was undiagnosable until the order was reversed; with the record written first, the same run reported `NATIVE CRASH: code=0xC00000FD` and named its address, which is the whole of what was needed. The backtrace is still attempted, appended as a second write — panic_log opens for append, so a crash between the two costs the backtrace and nothing else.
rust-i18n builds its backend once, lazily, on the first t!() — and its generated initializer materialises all ~2600 keys in ONE stack frame. Whether that frame fits depends entirely on where the first lookup happens, which nothing was choosing deliberately: it landed deep inside chart-tab construction, the __chkstk probe entering the frame faulted, and the application died at startup with 0xC00000FD before opening a window. The threshold moved because the dictionary grew — this branch adds thirteen keys and #441 added the field-label table — but the trigger is the call site, not the count, so a dictionary this size will cross it again from anywhere. Touched at the base of the stack instead, the frame is built where there is room and every later t!() is a map lookup. Verified live: the same configuration that crashed on every launch now starts.
guyverino
force-pushed
the
feat/strategy-order-and-folders
branch
from
September 6, 2026 20:04
67ea65b to
939e7ce
Compare
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.
What & why
Two halves of one story, both of them things moonproto only started synchronizing in
2e67562and this terminal had no way to represent before.
Strategy order. A core's strategy list is an arrangement the operator made in MoonBot, and the
library now carries it as the row sequence of a Full snapshot. The tree used to throw it away,
sorting folders into a byte-wise alphabet that put
Zetaabovealphaand every Cyrillic nameafter every Latin one. A folder now sits where its first strategy sits in the core's own list —
taken from the unfiltered list, so typing in the search box cannot rearrange the tree around the
reader.
The operator can also change that order now: two footer buttons,
Ctrl+Shift+Up/Down, and twocontext-menu items move the selection inside its folder, and the result goes to the core as a
complete id sequence (
CoreCmd::ReorderStrategies). Until the core echoes it back the tree drawsthe sequence that was sent — without that, a second press would be computed from the arrangement
the first one had already replaced. That same pending sequence is applied to every other
outgoing strategy sync too, or a checkbox toggled in the meantime would hand the core back the
order the operator had just replaced.
Folders, including empty ones. The core keeps a versioned folder tree now, so "new folder" is
a real edit rather than a mark that lives until the window closes. Edits travel as intents — add
this folder, move that subtree — and the complete desired tree is assembled on the feed thread,
where the newest tree is known. That is the whole design point: the wire form deletes every folder
the list omits, so a tree assembled from a window's snapshot turns a create into a silent delete of
whatever arrived meanwhile. A rename sends the rewritten tree together with the rows, or the
emptied old path stays behind on the core as a folder of its own.
Notable decisions
Ctrl+Shift+Up/Down, notAlt+Up/Down. The latter ships as the binding for "shift the sellorder's price" (
moon_core::config::hotkeys). One chord with two meanings is a trap even whereit cannot fire twice today.
CoreFolders::supportedmeans the core keeps a tree and itsempty folders can be drawn;
editablemeans every path in that tree would also survivemoonproto's validator. They differ on a real account: MoonBot allows a
/inside a folder name,moonproto's state then reports the split halves as parent folders, and its validator refuses
those — so on such a core every folder edit would be refused whole, bundled strategy moves
included. The terminal asks once and keeps its local marks instead of failing per command.
AddFolder/RemoveFolder/MoveStrategies { rebase }carry a path or a subtree;
feed::folder_treeapplies it to the newest tree the feed knows,which is the one it last sent while the core has not answered.
feed::folder_tree::sendableis ahand-kept mirror of moonproto's
validate_strategy_folder_paths, checked line by line againsthandles.rs:1572.destination's run, because the protocol asks a folder's strategies to stay contiguous and the
tree places a folder where its first strategy appears — a row left at its old index could drag
the whole folder somewhere nobody asked for. A rename is deliberately exempt: its anchor is a row
that was not part of the move, so it relocates nothing.
not synchronized, so there is nothing to follow; MoonBot's own order is its own. Confirmed with
the owner against a live core.
anything — while reserving their space so captions keep their column.
Known limitations
confirmation window closes, without saying why; no strategy command has a result path back to the
window today.
analytics/purge.rsstill deletes an emptied folder the legacy way. Now that a folder is a thingthe operator can keep deliberately, that deserves its own look.
match.
MoonBot's duplicate nodes cannot be told apart — confirmed with the owner.
BB1: a folder created in the terminal reached the core and came back, and the startup crash above
was found and confirmed fixed on that same account.
Two startup fixes this branch carries as well
Driving the branch on a real account crashed the application at startup, every launch, before a
window appeared. Both fixes below are independent of the strategy work and needed on
mainregardless of it.
fix(startup)— rust-i18n builds its backend once, lazily, on the firstt!(), and itsgenerated initializer materialises all ~2600 keys in ONE stack frame. Whether that frame fits
depends on where the first lookup happens, which nothing was choosing: it landed deep inside
chart-tab construction,
__chkstkfaulted entering the frame, and the process died with0xC00000FD. The dictionary crossing that threshold is a shared debt — this branch addsthirteen keys, feat(strategies): human labels under Moonbot's own identifiers, every field #441 added the field-label table — but the trigger is the call site, so the same
size will cross it again from anywhere. Forcing the backend at the base of
mainbuilds theframe where there is room.
fix(diagnostics)— both crash handlers captured a backtrace before writing their record.Symbolizing goes through
dbghelp, which cannot run on an exhausted stack, so the capturefaulted and the process died inside the handler with NOTHING written: empty
panic.log, emptyapp log, and a Windows crash record blaming
dbghelp.dll. Writing the record first is what madethe overflow above diagnosable at all — the same run then reported its code and address.
How to verify
Green on the rebased tree: moon-core 1654, moon-ui-gpui 1492 + 319 (theme_contract), fmt
clean, and zero new clippy findings in the touched files — the repo's pre-existing clippy debt is
unchanged. FireTest not run: this change touches neither chart, render, windows nor input.
By hand, on a core that keeps a folder tree: rename a populated folder (no ghost left at the old
name), drag an empty folder, delete a populated one (its rows go with it) and an empty one, and
create a folder on an old core (stays local, and its tooltip says so).