fix(export): address adversarial review findings in PR #60 PoE2 build export#76
Conversation
CRITICAL
- PassiveSpec: include nodeNotes in CreateUndoState/RestoreUndoState so
Ctrl+Z and tree copy no longer silently erase per-node notes
- BuildExportPoE2: replace io.popen('ls -d ...') in dirExists with
os.rename(path,path) — eliminates the Windows crash risk and the shell
injection vector from unescaped path content
HIGH
- BuildExportPoE2: remove warnedNoStringId module-level global; surface
the "no stringId in tree.lua" warning as a third return value from
Export/WriteFile so each call site gets fresh state and the UI can
display it prominently (ImportTab now shows colorCodes.WARNING status)
- BuildExportPoE2: sort `order` before emitting in buildPassives and sort
SlotMap keys before iterating in buildItems to produce deterministic
JSON output regardless of LuaJIT hash table iteration order
MEDIUM
- BuildExportPoE2: PresetNextLevels anyHas now checks `levelMin or
levelMax` (was levelMax-only), preventing double-seeding of an entry
that already has only levelMin set
- BuildExportPoE2: autoBracket uses m_max(lo, ...) for hi so hi >= lo
is guaranteed even for n > 99 sets
- SkillsTab: add local m_floor alias; fix clampLvl fallback to use it
instead of the global math.floor; fix level-bracket callbacks to set
self.modFlag instead of self.build.modFlag
LOW
- PassiveTreeView: gate the per-node note popup and tooltip on
hoverNode.alloc — notes only make sense on allocated nodes; tooltip
shows existing note content when present
- BuildExportPoE2: replace factually-incorrect "typo in gameId" comment
on gemIdFor with an accurate description
Tests: add cases for levelMin-only anyHas, Export warning return value
https://claude.ai/code/session_01EvGnnyxkoMawLmWWqCTxxX
🔄 Cycle 1, attempt 1/3
Missing test — No test exercises the ✅ Agent succeeded (cycle 1, attempt 1) |
- ImportTab: fix safePath gsub no-op ('^' -> '^^' to actually escape
caret characters for PoB's color-code renderer)
- BuildExportPoE2: make dirExists locale-independent by checking only
ok == true from os.rename; error strings are locale-sensitive and
cannot reliably distinguish ENOENT from EACCES
- Tests: add Export test covering the passiveWarning non-nil path
(allocated node with no stringId triggers the degraded-export warning)
https://claude.ai/code/session_01EvGnnyxkoMawLmWWqCTxxX
|
🔄 Cycle 1, attempt 1/3 PassiveSpec.lua:2396 — BuildExportPoE2.lua:68 — The ✅ Agent succeeded (cycle 1, attempt 1) |
Adds an Export test that places 100 passive specs with one allocated node in the first spec, then asserts the emitted level_interval has lo <= hi. Regresses the underflow that existed before the m_max fix (n=100, i=1 gave hi=floor(1/100*99)=0 < lo=1). https://claude.ai/code/session_01EvGnnyxkoMawLmWWqCTxxX
|
🔄 Cycle 1, attempt 1/3 ✅ Agent succeeded (cycle 1, attempt 1) |
Summary
Fixes all issues identified in the adversarial review of PR #60. Changes are scoped strictly to the export feature branch (
feature/poe2-build-export-improved).Fixes applied
CRITICAL
nodeNoteslost on undo/tree-copy (PassiveSpec.lua): AddednodeNotes = copyTable(self.nodeNotes)toCreateUndoStateandself.nodeNotes = state.nodeNotes or {}inRestoreUndoState. Without this, every Ctrl+Z after setting a node note silently erased it, and copying a passive tree lost all notes.io.popen('ls -d ...')crash + injection (BuildExportPoE2.lua): Replaced withos.rename(path, path)— a POSIX-guaranteed no-op when the path exists. No shell is spawned, no path content is interpolated into a command string. Eliminates both the Windows crash risk and the shell injection vector.HIGH
warnedNoStringIdmodule global (BuildExportPoE2.lua): Removed the per-session warn-once guard. The warning is now returned as a third value frombuildPassives→BuildTable→Export→WriteFile, so every call site gets fresh state.ImportTab.DoPoE2Exportnow displays the warning as acolorCodes.WARNINGstatus instead of silently succeeding.BuildExportPoE2.lua):table.sort(order)added inbuildPassivesbefore emitting;buildItemsnow iterates a sorted copy ofSlotMapkeys. Eliminatespairs()-over-hash-table ordering surprises.MEDIUM
PresetNextLevelsanyHas checks onlylevelMax(BuildExportPoE2.lua): Changed tolevelMin or levelMaxand guarded themaxLvlupdate withentry.levelMax and— prevents double-seeding an entry that already has onlylevelMinset.autoBracketgiveshi < lofor n > 99 (BuildExportPoE2.lua): Changedhiformula to usem_max(lo, m_floor(i/n * 99)), guaranteeinghi >= lofor anyn.SkillsTabclampLvl fallback uses globalmath.floor(SkillsTab.lua): Addedlocal m_floor = math.floorat module top; fallback now uses the local alias consistently with the rest of the file.self.build.modFlagvsself.modFlag(SkillsTab.lua): Level-bracket edit callbacks now correctly setself.modFlag(tab-level dirty flag) instead ofself.build.modFlag.LOW
PassiveTreeView.lua): Gated both the Shift+Right-Click popup and the tooltip hint onhoverNode.alloc/node.alloc. The tooltip now shows existing note content prominently when present, with the edit hint below.gemIdForcomment (BuildExportPoE2.lua): Replaced the factually incorrect "typo in gameId" claim with an accurate description of whygemIdis preferred overgemData.gameId.Test plan
docker compose run --rm busted-testsPresetNextLevelstest case (levelMin-only entry) passesExportwarning-return test passeshttps://claude.ai/code/session_01EvGnnyxkoMawLmWWqCTxxX
Generated by Claude Code