Skip to content

feat(strategies): human labels under Moonbot's own identifiers, every field - #441

Merged
kirillDevPro merged 3 commits into
mainfrom
feat/strategy-field-labels
Sep 6, 2026
Merged

feat(strategies): human labels under Moonbot's own identifiers, every field#441
kirillDevPro merged 3 commits into
mainfrom
feat/strategy-field-labels

Conversation

@kirillDevPro

Copy link
Copy Markdown
Collaborator

What & why

Strategy fields showed only Moonbot's identifiers and the Sections list only Russian names. Every field row now leads with the identifier and carries a human label under it in the user's language (all 414 fields the local replica has ever seen, ru/en/es); sections read Main · Основные in the heading and identifier-over-label in the list. The tree footer buttons are «Start»/«Stop» with the «checked strategies» qualifier in a tooltip so a narrow tree keeps its captions, and the 57/57 (50) counters explain both numbers on hover.

How to verify

  • Strategies → pick a strategy: each field reads MaxPing over «Макс. пинг»; unknown fields keep one line.
  • Sections column: Filters / Ping over «Фильтры / Пинг»; heading Filters / Ping · Фильтры / Пинг.
  • Click empty tree space: only the parameters pane says «select a strategy».
  • cargo test -p moon-ui-gpui --test theme_contract — label table ↔ dictionary bijection at 414.

https://claude.ai/code/session_01RaQ2tnAyr4yiwGktPBv

The Strategies editor speaks Moonbot's internal language: every field
row prints the raw identifier (SilentNoCharts), the sections list shows
schema names (Filters / Price/Position), the tree's 57/57 (50) counts
are unexplained, three surfaces repeat the same empty-state sentence,
and the footer buttons read as sentence fragments.

Add a fail-closed label table beside the existing tooltip table: a
field with a known label shows the human name with the raw identifier
as a muted subtitle (194 of 216 fields; the 22 nobody can name honestly
keep the raw name), sections get translated titles with the schema name
in a tooltip, the orders count in the tree gets its own tone, only the
parameter pane keeps the empty-state sentence, and the footer buttons
read "Запустить" / "Остановить" with "отмеченные стратегии" in the
tooltip. Handlers are untouched.

Claude-Session: https://claude.ai/code/session_01RaQ2tnAyr4pyiAiwGktPBv
A field row led with the Russian label and kept Moonbot's identifier as
a caption, the reverse of what a trader reading the manual or a forum
post needs, and 220 fields the label table had no arm for rendered as
a bare identifier: the whole MoonShot, MoonStrike, Hook, Liq, VLite and
Waves families among them.

Every row now leads with the identifier and carries the human name as a
caption under it (or in the tooltip in the fixed-pitch full view). The
table gains a label for every field the local strategy replica has ever
seen, in all three languages, and its help key becomes optional: a
field known only from the replica has a name but no dependency note.
The label-lookup contract reads every label token in the table and pins
the new count.

Claude-Session: https://claude.ai/code/session_01RaQ2tnAyr4pyiAiwGktPBv
@kirillDevPro
kirillDevPro merged commit 6fb9c00 into main Sep 6, 2026
@kirillDevPro
kirillDevPro deleted the feat/strategy-field-labels branch September 6, 2026 18:01
guyverino added a commit that referenced this pull request Sep 6, 2026
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 added a commit that referenced this pull request Sep 6, 2026
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 added a commit that referenced this pull request Sep 6, 2026
…ers (#445)

* feat(strategies): follow the core's strategy order, and keep its folders

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.

* fix(diagnostics): record what crashed before capturing its backtrace

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.

* fix(startup): build the translation backend before the stack runs out

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.
kirillDevPro added a commit that referenced this pull request Sep 7, 2026
…#447)

The localized name under every section title and field identifier (#441)
is useful to someone reading a strategy in their own language and noise to
someone who thinks in Moonbot's identifiers and wants a denser pane. Both
are real users of the same window, so the caption becomes a preference in
the Strategies settings gear rather than a fixed part of the row.

It ships ON: the captions arrived on by construction, so keeping them is
what leaves an upgraded pane unchanged. Off, the sections pane keeps one
line per section, the parameters header reads `Main` instead of
`Main · Основные`, and a field row keeps only the name the core speaks —
the help tooltip is unaffected, it never was a translation of the name.

Persisted as one optional lenient key in layout.toml next to the other
Strategies preferences, resolved through the same PrefRow table, so an
absent key keeps the default and a malformed hand edit cannot discard the
surrounding layout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant