Add a Blueprint Info editor: name, icons, description, and grid alignment - #222
Add a Blueprint Info editor: name, icons, description, and grid alignment#222koenigstag wants to merge 18 commits into
Conversation
The blueprint's own name, description, and icons had no editing UI at all: name was a bare mutable field nothing wrote to interactively, description was explicitly marked "unused" (round-tripped but never read or written by the app), and icons only had a private auto-generation path (generateIcons, used by serialize() when none were imported). Adds: - Blueprint.name/description as proper get/set pairs routed through history (undo/redo support), each boxed the same way scheduleStore already is - `history.updateValue` needs `keyof T` on whatever it's handed, and `keyof this` never includes a private member. - Blueprint.getIcon/setIcon, wrapping the existing icons Map with history.updateMap. Clearing all four slots by hand is a real choice rather than a no-op: it puts the blueprint back into the same "auto" state generateIcons uses when nothing was ever set. - BlueprintInfoEditor, a Dialog (not Editor - there's no Entity here) with a name field, a multi-line description field, and four icon slots (BlueprintIconSlot, a Slot like every entity editor's icon pickers) backed by a new FD.acceptedSignalIcons() - items, fluids, and virtual signals. - BlueprintInfoButton, a persistent corner button that opens it - positioned left of EntityInfoPanel's own 270px width so the two, both anchored top-right, never overlap. - TextInput gained optional multiline/height parameters (backing a <textarea> instead of an <input>) for the description field - every other existing call site is unaffected, since both default off. InventoryDialog changes, needed by the icon picker rather than by blueprint info itself - the same computeWidth/scrollbar-anchor/ showRecipePanel changes as the display-panel editor PR, ported here independently since this branch does not depend on that one: - computeWidth widens the dialog when a filter populates more group tabs than the fixed 404px layout was designed for - the icon picker's items+fluids+signals filter is the first caller on this branch to hit that, up to 7 tabs against Space Age data. - The scrollbar thumb was pinned to a fixed VP_X + VP_W offset, correct only at the old fixed width; anchored it to `this.width` instead. - A showRecipePanel flag (default on) lets the icon picker skip the recipe panel, which would otherwise render as a permanently empty bar - none of items/fluids/signals have a recipe.
Right-docked (left of EntityInfoPanel) worked but sat far from everything else in that corner. Left of the website's own DOM overlay (FBE logo, Discord/Github buttons) puts it next to the rest of the persistent chrome instead, and the position no longer depends on screen width, so the resize listener is gone with it.
New "Snap to grid" section in BlueprintInfoEditor - grid size
(Width/Height), grid position (X/Y), and an Absolute/Relative choice -
covering `snap-to-grid`, `absolute-snapping` and
`position-relative-to-grid`, which the editor already round-tripped on
import/export but never exposed or let anyone edit.
Verified the exact serialization rules by decoding three real
exported blueprint strings by hand (Absolute, Relative with a default
position, and grid off) rather than guessing:
- Absolute writes `absolute-snapping: true`.
- Relative at the default `{0, 0}` position omits both
`absolute-snapping` and `position-relative-to-grid` entirely -
`false` and `{0, 0}` are Factorio's own omitted defaults, not
something to write out.
- Grid off omits all three keys together.
`Blueprint.absoluteSnapping`'s default flips to `false` accordingly,
and `serialize()` now gates `absolute-snapping`/`position-relative-to-grid`
on `snap-to-grid` being set (and the position on being non-zero)
instead of forwarding the boxed stores directly - otherwise turning
snapping back off could leave a stale `absolute-snapping: true` behind
from before it was, which the old direct-forward version would have
done since nothing cleared it.
Also: enabling "Snap to grid" from off always selects Absolute, which
is what the game's own default does (confirmed the same way - a
freshly-enabled-and-exported string always carries `absolute-snapping:
true` rather than omitting it).
New `RadioButton` control (circular, mutually-exclusive-by-convention)
for the Absolute/Relative choice - `Checkbox` draws a rounded square,
which reads as an independent toggle rather than one of two options.
Icons moved above Description in the dialog, per request.
.disabled already stopped typing (the real DOM disabled attribute
underneath), but nothing dimmed them: TextInput's own box config
comments out a 'disabled' style, and .alpha only reaches the box
graphic - _updateDOMInput's opacity line is commented out too, citing
a pixi.js worldAlpha/DOM sync issue - so a disabled field looked
identical to an editable one. setInputStyle('opacity', ...) reaches
the DOM element's own CSS directly instead, which the other two routes
each miss half of.
Grid position's X/Y now stay editable whenever the grid itself is on, regardless of which of Absolute/Relative is picked - only Blueprint.serialize cares which one is chosen, so switching between them and back doesn't lose whatever was typed there. The decorative Absolute row's X/Y (no Blueprint field backs them - see the comment where they're built) go the other way: enabled only while grid is on *and* Absolute is the active choice, disabled otherwise, since there's no data behind them to preserve across a switch.
actual content width instead of guessed coordinates Two bugs from the same root cause: Grid size's Width/Height weren't flush with the dialog's right edge, and Grid position/Absolute's X/Y ran past it entirely. Both rows used fixed x-coordinates guessed from a mock-up rather than derived from the dialog's real content width - 336px, the same width BlueprintInfoEditor's Name/Description TextInputs already use. COL1_X/COL2_X now anchor the two value columns' input boxes to that width, and every field label (Width:/Height:/X:/Y:, four of them across three rows) right-aligns to its own input at a fixed 3px gap via makeFieldLabel, rather than each sitting at its own guessed x - which had put "X:"/"Y:" far from their input while "Width:"/"Height:" sat close to theirs.
…labels RadioButton draws its own label at local y=0, but every field label (Width:/Height:/X:/Y:) sits at its row's y+8 - the offset that centres it against the neighbouring 24px-tall input. Absolute's radio and its own X:/Y: label shared a row but not that offset, so "Absolute" read 8px higher than the text right next to it. Both radios now position at +8 too, which also centres their circle against the row's input boxes better than the unshifted position had.
Two loose ends the previous commit named and left, both now closed. `deploy` waits on `e2e` as well as `checks`. The class of bug the browser suite exists to catch is precisely the one that reaches production otherwise: PR #222 type-checks, lints and passes every unit test while dropping `position-relative-to-grid` from 325 of the corpus's 367 blueprints, and `checks` was green on it. Gating on `checks` alone leaves that free to deploy. The price is about five minutes on the deploy's critical path. `serve` becomes a declared devDependency instead of something localpreview fetches with `npx --yes`. Unpinned was tolerable while that command only ran on a laptop; as a CI dependency it was a package resolved at run time in a job whose previous step is `vp install --frozen-lockfile`, which is the one thing that pipeline is meant not to do. It costs ~990 lines of lockfile for a dev-only static file server and buys a reproducible sprite server plus Renovate tracking it. Verified: `npx --no-install serve --version` answers 14.2.6, so nothing is fetched, and localpreview still brings both ports up - data.json 200 off 8081. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
The 192 browser specs ran nowhere but a maintainer's laptop, and the gap was not theoretical: PR #222 arrived with every check green and a blueprint-round-trip.spec.ts failure in it, dropping `position-relative-to-grid` from every absolutely-snapped blueprint - 325 of the corpus's 367. Neither `vp check` nor `vp test` can see that, because the decode -> model -> serialize path needs `FD` loaded, which needs a browser and the sprite server. Needs no Factorio: the corpus is committed under test-blueprints/ and the sprite data under packages/exporter/data/output. Three choices worth stating, since each has a wrong version that looks right: - Reuses `npm run localpreview` rather than carrying its own copy of how to start the two servers. A second copy is a bound written down twice, and the one that loses is the one nothing tests. - The browser cache is keyed on the *resolved* @playwright/test version read out of node_modules, not the caret range in package.json. The resolved version decides the browser build, so keying on `^1.62.0` serves yesterday's browser to today's Playwright - which fails as a missing chrome-headless-shell and reads as a suite-wide regression, not a cache miss. - The server step redirects stdout to a file instead of inheriting it. A backgrounded process holding the step's stdout pipe open can stop that step from ever completing. `deploy` waits on `e2e` as well as `checks`, so the #222 failure class cannot reach fbe.factorygamefan.com. `serve` becomes a declared devDependency rather than an `npx --yes` fetch - unpinned was fine on a laptop, and is not fine as the one run-time-resolved package in a job whose previous step is `vp install --frozen-lockfile`. Also corrects the eight comments across six files that asserted Playwright does not run in CI. The reasoning under each survives - a pure unit test still answers in seconds inside `checks` where this job takes five minutes and two dev servers - but the flat claim does not, and the split is now about cost rather than coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
|
I've updated the branch onto current base, which pulls in a CI job that didn't exist when you opened this: the Playwright suite runs on PRs now. It's running above. Locally it comes back 191 of 192, and I think the one failure is real.
Everything else in that fixture holds. All the counts, both position checksums. That narrows it to a field vanishing from the serialized JSON with no geometry moving. The field is 'position-relative-to-grid':
snapToGrid && !absoluteSnapping && !positionIsDefault
? positionRelativeToGrid
: undefined,It only writes when snapping is Relative. I decoded the corpus to see how often that branch is taken: of 367 blueprints, 325 carry snapping, and every one of them is Running the same probe against base and against this branch, the count of blueprints that keep a grid position through a round trip goes from 1 to 0. My guess is that One unrelated thing while you're in there: The alignment work itself is a real gap filled, and the serialization rules you worked out by decoding real strings are the right way to have got them. |
|
I said above that my guess was that The guess was right, and here is the table rather than the reasoning:
Set a grid position in relative mode and the game writes no position key at all. Set the same one in absolute mode and it writes it. So the condition wants to be I put both candidate rules into the probe and scored them against every measured row before writing any of this down. The inversion agrees on all seven rows that have a grid; the current condition disagrees on three. Two things from the probe worth having if you go near this. Setting And a grid position with no The rest of the review stands: the Absolute row's X/Y still needs backing by the real field rather than the placeholder, and |
Nothing pinned this. `blueprint-round-trip.spec.ts` caught #222 dropping `position-relative-to-grid`, but only by accident of the corpus: exactly one of its 367 blueprints carries a grid position, so re-capturing that fixture or swapping the corpus the way #186 did would take the coverage with it. And what it reports is a moved hash rather than a named field. The rule asserted here is the measured one from tools/oracle/fixtures/blueprint-snapping.json: the game writes `position-relative-to-grid` under absolute snapping and omits it under relative. Synthetic because it has to be. All 325 corpus blueprints that carry snapping are absolute, so a test built from real exports cannot tell the two modes apart - the same reason the corpus could not answer the question and the binary had to. Mutation-checked with PR #222's own condition pasted into `Blueprint.serialize`: two of the five fail and three pass, so the headline test is not decoration and the origin test is doing work of its own. The no-snapping test is a control that survives that mutation and would catch the opposite mistake, a default grid written onto every blueprint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
#226) PR #222 rewrites Blueprint.serialize to write `position-relative-to-grid` only under Relative snapping, which drops it from every real blueprint. The corpus can say the two co-occur - 325 of 367 carry snapping and all 325 are absolute - but it cannot say one requires the other, because no corpus blueprint uses relative snapping at all. That is the shape of argument #133 item 5 and #142 both got wrong, so this asks the binary. Measured on 2.0.77, the version the editor targets: the game writes `position-relative-to-grid` under ABSOLUTE and omits it under RELATIVE. Set a position of {3,5} in relative mode and the game writes no position key; set the same in absolute mode and it writes it. Both candidate rules were transcribed into the probe and scored against every measured row before any editor code was touched - the inversion agrees on all seven rows with a grid, PR #222's rule disagrees on three. The trap, and why the probe carries a per-setter trace: setting `blueprint_position_relative_to_grid` turns `blueprint_absolute_snapping` ON. The first run set snap, then absolute, then position, so the position write flipped absolute back on and relative mode was never reached. The readback control caught that the instrument was broken; only the trace said which setter did it. Three no-grid rows are recorded and deliberately not scored: a grid position with no `snap-to-grid` is a state the GUI cannot reach, and the game answers it inconsistently. #133 item 4's lesson. Also adds tests/blueprint-snapping.spec.ts, because nothing pinned this. blueprint-round-trip.spec.ts caught #222 only by accident of the corpus - exactly one of its 367 blueprints carries a grid position - and reports a moved hash rather than a named field. Synthetic, since all 325 snapping blueprints are absolute and real exports cannot tell the modes apart. Mutation-checked with #222's own condition pasted into serialize: two of the five fail, three pass. No editor behaviour changes. The existing pass-through is correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
|
Reopened. I closed this yesterday while merging #226, and there was no decision behind it. Sorry for the noise. Nothing has changed from the two comments above. The serialize condition still wants inverting to What has changed is that #226 is merged, so The alignment section is a real gap and I want it in. |
|
For whatever agent picks this up, here is the same thing in a form you can act on directly. Merge 1. Invert the serialize condition
// current
'position-relative-to-grid':
snapToGrid && !absoluteSnapping && !positionIsDefault
? positionRelativeToGrid
: undefined,
// wanted
'position-relative-to-grid':
snapToGrid && absoluteSnapping && !positionIsDefault
? positionRelativeToGrid
: undefined,This was measured against Factorio 2.0.77, not reasoned about. The game writes The 2. The UI half follows from the same rule
How that is presented is open. What is not open is that a number the user can type has to reach the exported string or be visibly unavailable. 3. Mobile guard
Verify before pushingBoth dev servers have to be up for the Playwright half, which is what |
…nto blueprint-info-editor
position-relative-to-grid belongs to Absolute snapping, not Relative - measured against Factorio 2.0.77 (issue teoxoy#226, tools/oracle/fixtures/blueprint-snapping.json), reversing the guess the original comment made from three hand-decoded strings. Inverts the condition in Blueprint.serialize() and reconciles BlueprintAlignment's two X/Y pairs into the one backed by the real field, enabled only under Absolute. Also fixes positionIsDefault, which conflated an explicit {0, 0} with an absent position and stripped both - tests/blueprint-snapping.spec.ts pins that an explicit {0, 0} passes through unchanged, so this now checks only for undefined. And moves BlueprintInfoButton inside the `if (!isMobile.any)` guard in UIContainer, where it belongs alongside quickbarPanel/wiresPanel - the editor is view-only on mobile. Merges wormeyman-space-age-support in first, which is where blueprint-snapping.spec.ts and its fixture/probe landed.
…print-info-editor
|
Fixed the places you've mentioned. Inverted the serialize condition to Absolute (measured against 2.0.77, issue #226), reconciled the grid-position X/Y inputs into the one field it actually backs, moved BlueprintInfoButton inside the mobile guard, and fixed positionIsDefault stripping an explicit {0, 0} that blueprint-snapping.spec.ts expects to pass through — all three review items plus what the test caught, vp check/vp test and both named specs green. |
…Button The only way to switch entries in a loaded blueprint book was a bare "BP Book Index" number field in the settings pane, with no names, icons, or indication of nesting. BookDialog walks the book's raw entries directly (not the flattened index space Book.selectBlueprint reads) and renders them as an always-expanded tree - nested books get a header row and indented children, planners get a dimmed placeholder row, and a depth guard of 10 stops runaway recursion on a malformed book. Rows scroll with the same mask+wheel+thumb pattern InventoryDialog already uses. BookButton opens it, sitting one slot pitch right of BlueprintInfoButton and visible only while a book is loaded, rather than living in ToolsPanel (teoxoy#221) - this feature has no real dependency on that PR's work, so it gets its own minimal QuickActions (getCurrentBook/selectBookEntry) instead of pulling in the larger interface teoxoy#221 introduces. Editor.init takes a single options object for the same reason teoxoy#221 landed on that shape: a required quickActions and an optional logger don't have to fight over positional order. Also collapses three copies of the same index-switching logic (the settings pane callback, testApi.selectBookIndex, and now QuickActions.selectBookEntry) into one function.
…rintInfoButton" This reverts commit 392d2f4.
|
Re-reviewed against the base branch as it stands now. All four items are fixed, and I checked each one against the code rather than taking the summary. The one worth calling out is the Two things left.
|
…o-grid edge case Entity.acceptedDisplayPanelIcons duplicated acceptedSignalIcons() in factorioData.ts verbatim; it now calls that function instead of recomputing the same list. Also adds a sixth test to blueprint-snapping.spec.ts pinning that absolute-snapping/position-relative-to-grid are stripped on serialize when there is no snap-to-grid to be relative to - a state the in-game GUI cannot reach, and one the game's own measured fixture shows it cannot reliably round-trip either.
TextInput is the one control here not drawn with pixi: it appends an <input> to document.body and keeps it positioned over the canvas, so nothing pixi draws on top can occlude it. Opening the icon picker over Blueprint Info left six fields showing through it - the name box printing "Blueprint" across the picker's slot row, and five more invisible only because their background is none, all still answering elementFromPoint and so eating the clicks aimed at the slots underneath. Dialog now hides the DOM fields of every dialog that is not the topmost, and gives them back when the one above closes. Topmost rather than "not overlapped" because occlusion is not a question the DOM can answer for canvas content - there is no partial hiding to be had, so the dialog stack decides it. The walk is recursive since BlueprintAlignment's inputs sit in a nested container. TextInput._dom_visible already existed with no way to set it; domVisible applies the change immediately rather than waiting for a render, because _needsUpdate watches only the transform and the canvas rect and a field that never moves again would keep its old visibility forever. Covered in tests/text-input.spec.ts, which is the only spec that asserts on real DOM. Mutation-checked: dropping the call from the constructor fails only the hidden assertion, dropping it from close() only the restored one. Its reads sync on a frame rather than a timeout - _onAdded hides the element and only onRender puts it back, which made a first draft fail 1 run in 6, and an unrendered frame would satisfy the hidden assertion for the wrong reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A real game screenshot showed two separate X/Y pairs in the grid alignment section - "Grid position" above the Absolute/Relative choice, and a second pair beside "Absolute" itself - where the editor only had one. Decoding blueprint strings exported at each step of a manual game test (not reasoning about it) settled what each one actually does: typing into "Grid position" writes no blueprint field at all, neither snap-to-grid, absolute-snapping nor position-relative-to-grid. It moves every entity's own position by the negation of what was typed, baked directly into the exported entity coordinates, then resets to 0. "Absolute"'s own X/Y is the one that round-trips as position-relative-to-grid. Entity.forceMoveBy bypasses position's collision/wire-reach checks, which compare a moving entity against every other entity's current position - unsafe mid-shift, where that's a mix of already-moved and not-yet-moved entities, even though a uniform translation can't actually introduce a collision or break a wire's reach. Blueprint.translateEntities wraps every entity's forceMoveBy in one undo transaction. BlueprintAlignment's layout now mirrors the game: Grid position gated only by Snap to grid, Absolute's X/Y on its own row gated by Snap to grid and Absolute being selected.
TextInput's 'changed' event fires on every keystroke (the DOM 'input'
event), not on blur. commitGridPositionNudge() resets both fields' text
back to '0' on every commit, so wiring it to 'changed' meant the field
wiped itself back to '0' after the very first character typed - it
looked like the box refused to hold more than one digit.
TextInput._onBlurred already called _setState('DEFAULT') but had its
emit('blur') commented out; uncommented it and moved Grid position's
commit there instead; blur fires once per edit rather than once per
keystroke. The other fields in BlueprintAlignment stay on 'changed' -
their commits re-write the blueprint with the current full text rather
than clearing anything, so repeating them per keystroke is harmless.
Updates text-input.spec.ts's DOM field count/visibility assertions,
which pinned six TextInputs in Blueprint Info before Grid position
added two more.
|
Re-reviewed at I drove this one in a browser instead of only reading it, because the new "Grid position as a real entity shift" work is the kind of change a green suite cannot speak for. Both dev servers up, throwaway probe, numbers printed. The first five items below are all measured that way rather than reasoned about, and the first two are why I am not merging yet. 1. "Grid position" does not change the exported blueprint at allTwo chests, one nudge of (-3, -4): The model moves. The exported coordinates do not move at all.
2. On a blueprint with tiles, the nudge slides entities off the floor
Two chests plus two concrete tiles, nudge of x = 3: Entities moved one tile left. Tiles moved two tiles right. Re-import that and the machines stand three tiles away from the concrete they were built on. So the feature does nothing on a blueprint with no tiles, and damages one that has them. I think that points at something structural: a grid nudge cannot be an entity translation while 3. Opening Blueprint Info crashes on five of the twelve corpus files
Measured against Loading that file and clicking the corner button: The dialog never opens, and it throws again on every later click. There is a second effect worth knowing about, because it outlives the click.
So the keypress after the crash is swallowed. The control opens the inventory on the first press, which is what makes the treatment mean something.
4. A negative Absolute X or Y cannot be typed, and the wrong sign is exportedSnapping on, no It is not a first-try problem that clears up on a retry. It never works, and the exported value carries the opposite sign to the one the user asked for. The loop is: The comment at BlueprintAlignment.ts:197-204 explains why the other fields are safe on 5. Clearing the Grid Width box exports a grid the game will not take
Smaller things
Every field is on
The class doc has the ordering backwards. BlueprintInfoEditor.ts:72-73 says
Where this leaves itThe name, icons and description half is good work and I want it. The snapping half is right too, and the fix you made against my advice on Items 1 and 2 are the blockers. Item 3 is a crash on real files people will load. Items 4 and 5 are a single mechanism, the commit-and-refresh loop, and closing that closes both. One thing I would raise before the code changes: item 1 suggests the game's own "Grid position" may not be an entity translation at all. What the probe in #226 measured is which snapping mode carries |
|
I said in my last comment that the premise under "Grid position" was worth asking the game about before changing any code. I have now done that, on 2.0.77, the same binary #226 was measured on. The premise is refuted. Setting a grid position does not move entities. Here is the table rather than the reasoning. Two chests at (0.5, 0.5) and (8.5, 8.5), snapping on, absolute:
Nothing moves. Not in So there is no second thing to model. The probe is The control is the part worth readingA result like this is mostly zeros, and zeros are exactly what a broken probe produces. So there is a positive control: a Without that, "the coordinates never changed" would read the same whether the finding is real or the probe is comparing a value with itself. That is the #133 item 4 lesson in its null-result form, and it is the only reason the zeros above are evidence. One new thing about the API, which confirms #226 from the other end#226 found that setting This probe found the reverse: setting That is worth knowing for two reasons. It means a relative row cannot be scored here, because it holds no grid position by the time anything is read, so asking whether its grid position moved the entities is a question about a value that is not there. Those rows are recorded and left unscored rather than counted as "did not move". And it is independent confirmation of #226's result from the opposite direction: relative snapping does not carry a position because the game takes the position away, not merely because the exporter drops it. What this means for the PRItems 1 and 2 in my last comment are not bugs to fix. They are a feature to remove. Deleting the "Grid position" row, That leaves the crash on planet icons, and the commit-and-refresh loop behind the negative-value and zero-width bugs. Those are real and still want fixing. They are also much smaller than what just went away. Sorry for the round trip on this one. The screenshot reading was a reasonable inference and I did not question it either until the export numbers came back identical. |
…d pin it (#230) PR #222 adds a second X/Y pair to the alignment dialog, "Grid position", on the premise that the game's own control writes no blueprint field and instead moves every entity by the negation of what is typed. That premise was inferred from a screenshot of the game's dialog, not measured, and it is why Blueprint.translateEntities and Entity.forceMoveBy exist. Measured on 2.0.77, the binary #226 used. Setting a grid position moves no entity: not in get_blueprint_entities(), not in the exported string, not after importing that string back. The game writes position-relative-to-grid and leaves every coordinate where it was, over positions of {3, 5} and {10, -7}. So there is no second thing to model, and #226 already measured all of it. The positive control is what makes a null result mean anything. Four of the seven cases report "nothing changed" whether the probe is right or comparing a value with itself, so shifted-entities places the same two chests three tiles left and four up through set_blueprint_entities, touching no snapping property. Its exported coordinates differ from the baseline, which is the only reason the zeros count as evidence. The #133 item 4 lesson in null-result form. One new API fact, which confirms #226 from the other end. That probe found that setting blueprint_position_relative_to_grid turns blueprint_absolute_snapping on. This one found the reverse: setting blueprint_absolute_snapping to false clears the position. The readback control caught it on the one relative row, which holds no position by the time anything is read, so that row is recorded and left unscored rather than counted as "did not move". Relative snapping cannot carry a position because the game takes it away, not only because the exporter drops it. Two consecutive captures are byte-identical, so a third that disagrees is a finding rather than noise. Also adds the probe-blueprint-snapping.mjs row that #226 left out of the script table. Claude-Session: https://claude.ai/code/session_01DxZFeyzpXmxjwVHXxDo8zb Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A vitest text scan over tests/*.spec.ts for `Control+A` and its four siblings. On macOS `Control+A` is the emacs "beginning of line" binding, not select-all, so nothing is selected, the typed text lands beside the old text, and the assertion reads "New textOld text". That is #197, fixed in 402fbe3. It reads source rather than driving a browser because the bug **passes on Linux**, and every runner this project has is ubuntu-latest - `vp test` and all four `e2e` shards. #223 and #225 put Playwright in CI, which does not help here: a sharded browser suite gating every PR will go green on this forever. The rule is not "never write Control". A chord reaching the app through actions.ts stays Control, where ModifierKey is Control | Shift | Alt and no Meta binding exists; a chord reaching a focused DOM <input> wants ControlOrMeta. A regex cannot separate them, so it flags both and the ALLOWLIST carries the distinction in writing - one entry today. Mutation-checked against the live instance: dropping PR #222's blueprint-grid-position.spec.ts into tests/ fails the guard naming line 97, and removing it goes green again. The knowledge already existed in prose. After 402fbe3 there is a five-line comment at display-panel-editor.spec.ts:159 explaining the whole trap, one line above its own corrected call, and a contributor then wrote `Control+A` in a different file. A comment at the scene of the last occurrence does not reach the next one. Every figure in the new file's header is measured rather than recalled, which caught two: the display panel comment is five lines and not three, and four specs hold `keyboard.down('Control')` for the ctrl-drag gesture and not five. Both came from #208's own write-up. Part 1 of #208. Deliberately not `Closes`: that issue's part 2, the display panel editor's uncovered surface (the alt-mode checkbox, the icon picker and the connected branch), is untouched here and is real work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mg2oe7JgL3xEzSoN5emTsd
A vitest text scan over tests/*.spec.ts for `Control+A` and its four siblings. On macOS `Control+A` is the emacs "beginning of line" binding, not select-all, so nothing is selected, the typed text lands beside the old text, and the assertion reads "New textOld text". That is #197, fixed in 402fbe3. It reads source rather than driving a browser because the bug **passes on Linux**, and every runner this project has is ubuntu-latest - `vp test` and all four `e2e` shards. #223 and #225 put Playwright in CI, which does not help here: a sharded browser suite gating every PR will go green on this forever. The rule is not "never write Control". A chord reaching the app through actions.ts stays Control, where ModifierKey is Control | Shift | Alt and no Meta binding exists; a chord reaching a focused DOM <input> wants ControlOrMeta. A regex cannot separate them, so it flags both and the ALLOWLIST carries the distinction in writing - one entry today. Mutation-checked against the live instance: dropping PR #222's blueprint-grid-position.spec.ts into tests/ fails the guard naming line 97, and removing it goes green again. The knowledge already existed in prose. After 402fbe3 there is a five-line comment at display-panel-editor.spec.ts:159 explaining the whole trap, one line above its own corrected call, and a contributor then wrote `Control+A` in a different file. A comment at the scene of the last occurrence does not reach the next one. Every figure in the new file's header is measured rather than recalled, which caught two: the display panel comment is five lines and not three, and four specs hold `keyboard.down('Control')` for the ctrl-drag gesture and not five. Both came from #208's own write-up. Part 1 of #208. Deliberately not `Closes`: that issue's part 2, the display panel editor's uncovered surface (the alt-mode checkbox, the icon picker and the connected branch), is untouched here and is real work. Claude-Session: https://claude.ai/code/session_01Mg2oe7JgL3xEzSoN5emTsd Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
One line in this PR is now an enforced check rather than a review note, so here is the heads-up before you hit it. #233 merged as The fix is the same one #207 made at await page.keyboard.press('ControlOrMeta+A')Why this needed a guard and not another commentI flagged this line in my review a few hours ago, and I want to be clear that the guard is not aimed at you. It exists because the same thing happened to me. #207 fixed exactly this bug, wrote a five line comment above the corrected call explaining the trap, and the comment still did not reach the next person writing a spec in a different file. That is the failure the guard is for. The mechanics, since your CI is green today and will stay green on this: on macOS The rule it encodesNot "never write Control". There are two cases and they want opposite things:
A regex cannot separate those, so the test flags both and an |
|
My last two comments point at the same file in opposite directions, so here is which one wins. The deletion is the one to act on. If Sorry for the crossed wires. I posted the guard heads-up without re-reading what I had written above it. |
…#222 review items Grid position's premise was refuted by review: Blueprint.serialize() re-centres every exported position on getCenter()'s bounding box, recomputed on every call. Translating every entity by (dx, dy) moves that box's centre by exactly (dx, dy) too, so subtracting the shifted centre from the shifted positions always reproduces the pre-translation numbers - a uniform translation is invisible to a bounding-box recentre by construction, for any implementation built on moving entities. It also explains a second reported bug for free: translating only entities and not tiles let the two drift apart relative to each other once re-centred, since getCenter() averages both together. Replaces it with Blueprint.gridPositionOffset, an accumulated IPoint applied once inside serialize() against the already-computed centre - after re-centring, not before it, which a recentre cannot undo. Live entity/tile positions never move, so PositionGrid, rendering and every other model-level read are unaffected; only the exported string differs. Entity.forceMoveBy and Blueprint.translateEntities, built for the old approach, are removed as dead code. Also: - BlueprintInfoEditor: BlueprintIconSlot.updateContent() called F.CreateIcon() uncaught, which throws for a `space-location` icon (planet names - vulcanus, fulgora, gleba) since nothing in FD covers that category; took the whole dialog down on five corpus files. Wrapped in try/catch with a warning and a blank slot. - BlueprintAlignment: every text field now commits on 'blur' rather than 'changed' (which fires per keystroke). Width/Height/Grid position already had reasons to; Absolute X/Y needed it too - a leading '-' parses to 0 and was immediately echoed back by the post-commit refresh, so a negative value could never be typed at all. Also fixes a second bug the blur switch exposed: blurring Absolute X/Y after only tabbing through them (never typing) still wrote positionRelativeToGrid, turning "never set" into an explicit {0, 0} - a real difference to serialize(), not a cosmetic one. A dirty flag, set on 'changed' and checked before the blur-commit, makes an untouched blur a no-op again. - parseGridSize() floors Grid size at 1: clearing the Width/Height box parsed to 0 through parseGridValue and exported an invalid snap-to-grid the game will not accept back. - Checkbox and RadioButton were near-identical (constructor scaffolding, the checked-swap-graphics setter, hover wiring) apart from their drawn shape and what a click does to the state. Extracted the shared machinery into ToggleControl, taking the shape-drawing function and click behaviour as constructor arguments rather than overridden methods, so neither needs `this` before `super()` has run. - Exported BlueprintAlignment's layout constants and BlueprintInfoEditor's ALIGNMENT_X/Y instead of tests/blueprint-grid- position.spec.ts hand-copying them, so a layout change cannot silently desync what the spec clicks. tests/blueprint-grid-position.spec.ts is rewritten throughout: every assertion now reads the *exported* positions (encodeLoaded + decodeBlueprintString) rather than the live model, which is exactly the gap that let the original bug through - the model position did change under the old implementation, and nothing checked the string that mattered.
|
Closed PR by mistake. Reopening |
|
I have detached this repo and it is standalone now you may have to re-fork this repo and resubmit the PR's. @koenigstag |
|
trying to reopen |
Summary
history.updateValueso edits are undoable like everything else.snap-to-grid,absolute-snappingandposition-relative-to-grid, the three blueprint-string fields the editor already decoded/re-encoded but never exposed:absolute-snapping: truebehind in the exported string.RadioButtoncontrol (circular) for the Absolute/Relative choice, sinceCheckboxdraws a rounded square that reads as an independent toggle rather than one of two options.Test plan
vp check/vp testpass