Repository navigation
feat(schematic): move and hide a symbol's field text - #489
Conversation
|
Ask first: repair the terminal issue reference and record the post-commit failure separately; do not rebase yet.
This PR overlaps #485 in |
|
Done, and no rebase — the branch is untouched, waiting on the order and refresh point you set after #442.
|
|
#442 has landed as Refresh base: current When convenient: reconstruct once onto @neusse — this is the base/order you said we would set after #442; override here if you had a different sequence in mind. |
52ed7ce to
27d5b39
Compare
|
This is no longer only a rebase. The reconstruction is done and sits on current Head: Base: The defectThe new In this repository's own and the tool reported The second consequence is worse than the wrong field. The writer's own check is textual and does see the nested token, so it correctly no-ops; the readback then reported No existing test could have caught either. Every What changed, and what did notThe readback now accepts either form. The writer is unchanged, and the feature itself was never broken: the token it inserts is honoured. Exporting SVG at KiCad 10.0.6 from a file carrying the property-level token and one carrying
Gate on this head: The new guard was neutered and watched to fail before being restored, as the others were. If you would rather review the reconstruction clean, say so and I will split the readback fix into its own PR on top of it. |
|
Please refresh this branch onto current The PR was correctly reconstructed at |
27d5b39 to
8e4bc01
Compare
|
Refreshed onto current Head: Clean rebase, no conflicts. Both commits kept their patch-ids, so nothing but the base changed. The Ten checks green on this exact head. |
neusse
left a comment
There was a problem hiding this comment.
Please make the existing-file placement parser strict and add the durable public API migration entry before admission.
Spec review passes #490 on exact head 8e4bc013c54ce64209bc0065ce3a95a70be22015, including the multi-unit contract, arbitrary field-text safety, committed readback, KiCad evidence, negative controls, and separate #499 accounting. Standards review found one file-safety hole:
set_property_placementparses an existing(at …)usingfilter_map, then defaults missing values to zero. An existing malformed(at bad 20 90)becomes[20, 90]; a rotation-only request can rewrite it as(at 20 90 NEW), silently moving the field. Require finite numeric x/y, and finite rotation when present, positionally; refuse beforecommit_commandif the existing placement is malformed. Add a byte-identical-on-refusal test using malformed file content (the current non-numeric request test is a different guard).- Add
docs/API_MIGRATIONS.mdcoverage for the additivefield_placements,unit, andunits[].field_placementscontract: absolute-mm coordinates, per-unit targeting, supported visibility forms, response-derived readback, and next-minor release impact. The PR body and tool-directory row are not the durable migration record used elsewhere. - Keep
FieldPlacementprivate; no other module consumes it, sopub(crate)is broader than needed.
The issue-first design gate was missed because the implementation preceded #490; record that as process learning, but no history rewrite is requested. The second commit subject is also slightly over the roughly-72-character guide; leave the published commits intact and keep future subjects shorter.
After these focused corrections, rerun all ten checks and return the new head. #489 remains the first sch_components PR; #485 stays behind it.
|
All three done. Head: 1. Malformed existing placement. You were right that the existing non-numeric test guards a different door. One thing there goes beyond what you asked, and I would rather name it than have you find it. I also refuse an 2. 3. A question your wording surfaced, which I have not acted on. "Refuse before Not rebased — the branch is still level with Ten checks green on this head. Process notes taken: the issue-first design gate, and the commit-subject length — this commit's subject is 59 characters. |
|
Ask first: please make these five focused corrections, then return the refreshed head SHA. Your latest commit completed the three items from the earlier review; these are the remaining findings from reviewing head
The conservative refusal of an The existing |
|
Queue refresh — the five-item correction request in the preceding maintainer comment remains current. Please make the four code/test corrections now, but avoid repeated rebases: after #502 and the maintainer-owned #481 land, reconstruct once onto the resulting This remains immediately ahead of #485 because both modify |
72db166 to
2b3e008
Compare
|
All four corrections done, reconstructed once. Head: 1 & 2. A malformed file now aborts the whole call, before The split reads "malformed" as unparseable content — a bad, missing or extra
One question on shape rather than behaviour: the abort reuses this handler's existing 3 & 4. One report, no action taken. Two other Unchanged as you directed: the Ten checks green on this head. |
|
The four requested corrections are present on head One final queue refresh is now needed because |
Nothing in the server could position or hide a placed symbol's Reference, Value or any other field. reset_schematic_field_positions only restores the library default, so a field that lands badly -- a rotated gate whose Value sits on its pin numbers -- had no fix inside the tooling, and the way round was to empty the Value and draw the text some other way. edit_schematic_component gains `field_placements`, keyed by field name, each entry setting any of x, y, rotation and hide. An omitted part is left as the file has it, so moving a field cannot silently unhide it. It is a separate argument from `fields` rather than an overload of it: a caller moving the Value text is not changing what it says, and widening one argument's value type would make the schema lie about what it takes. Coordinates are absolute schematic millimetres, as KiCad stores them. That makes them per-placement, so `unit` says which one is meant, and a position given for a component with several placed units and no unit named is refused -- writing one coordinate to each would stack a quad gate's four Values on a point and report success. `hide` writes `(hide yes)` as a direct child of the property, which is what eeschema writes on a placement; the `(effects (hide yes))` form belongs to lib_symbols definitions. Verified against KiCad 10.0.6, whose own writer round-trips the property-level token unchanged. The readback now reports each field's observed position and visibility, and every requested placement is compared against the committed file before the call reports success: this handler edits source text, and a search that matched nothing would otherwise pass silently. Every guard was neutered and watched to fail. Two of the tests passed while neutered on the first attempt and were rewritten: one accepted any error from a handler with several ways to fail, and one asserted a substring after an unwrap_or(0) that had quietly made the whole file the search window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… value Adversarial review found two ways the writer could corrupt a field, and checking the result against real KiCad output found a third defect in the readback. A field's value is arbitrary text and may contain "(at " or "(hide ". The searches ran over the whole property block, so a value like "mounted (at 45 deg)" matched first and the move rewrote the *value*: "mounted (at 11 22 0)". The result is still valid S-expression, so nothing downstream notices. Both searches now start after the value's closing quote. Removing the hide token deleted back to the preceding newline, which takes whatever else is on that line. On a file that writes `(at 1 2 0) (hide yes)` together -- not KiCad's own layout, but one it is still asked to read -- unhiding a field destroyed its position. The deletion now takes the indentation before the token, and the newline only when the token had the line to itself. The readback looked for the hidden flag only among a property's direct children, which is the form lib_symbols uses. KiCad writes a *placement's* flag inside `(effects ...)`, so every field KiCad itself had hidden was reported visible. Worse, the writer's own check is textual and does see the nested token, so asking to hide such a field became a no-op that then failed its own post-write comparison with `stale_target` over a file that was already correct. The readback now accepts either form. The two are equivalent to KiCad 10.0.6: a file in each exports byte-identical SVG. The first two were caught by the post-write comparison rather than by any test, which is the argument for that check: each left a committed file that parses, exports a netlist, and is wrong. The third was found by reading what KiCad actually writes -- every existing test drove a field the fixture leaves visible, so none of them could have seen it. All three now have regression tests, and each guard was neutered and watched to fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of 8e4bc01 found a file-safety hole in the writer. An existing (at ...) was parsed with filter_map and the gaps defaulted to zero, so an unparseable token shifted the rest left: (at bad 85.09 90) read as x=85.09, y=90, and a rotation-only edit then committed (at 85.09 90 0) -- moving the field to a position the caller never asked for and the file never held, and reporting success while doing it. The placement is now parsed positionally and strictly: finite numeric x and y, a finite rotation when a third value is present, and a refusal when there are more values than that, since rewriting those as three tokens would drop whatever the extras carried. It refuses before commit_command, so the file is untouched. field_placement_refuses_a_malformed_existing_placement_without_writing corrupts the fixture's own (at ...) and asserts the file is byte identical after the refusal. That is a different guard from the existing non-numeric request test, which checks what the caller sent rather than what the file already contained. Neutered back to filter_map with unwrap_or(0.0) and watched to fail: it returns is_error false and the readback shows Value at x=85.09, y=90 instead of 157.48, 85.09. Also from the same review: docs/API_MIGRATIONS.md gains the additive contract for field_placements, unit and units[].field_placements, which the PR body and the tool-directory row do not durably record; and FieldPlacement drops to module-private, since nothing outside this file consumes it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of 72db166 found that a malformed existing placement refused only its own field. This handler edits text and commits once at the end, so a valid sibling edit in the same request was still committed while the call reported the refusal -- the file we had just found to be broken got written anyway. set_property_placement now returns a typed refusal instead of a String. MalformedFile aborts the whole call before commit_command; TargetMissing keeps the existing per-field behaviour. The split follows the request: "malformed" is unparseable content -- a bad, missing or extra (at ...) value, a broken property or hide token -- whereas a symbol, field or unit that is simply absent is the caller naming something that is not there, which is local to the field they named and was not asked to change. unit is also converted with u32::try_from rather than `as`. The integer cast truncates instead of refusing, so 4294967297 became 1 and edited a real unit the caller never named; overflow now returns a structured invalid_argument naming the value. Both guards were neutered and watched to fail. Removing the abort arm returns is_error false with the sibling committed and the placement demoted to an errors array. Restoring `as u32` moves unit 1's Value to (10, 10) and reports success. The two other unit casts in this file are in different tools and are f64 -> u32, which saturates rather than truncating, so they cannot reproduce this; they are reported separately rather than changed here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2b3e008 to
35a18bc
Compare
|
Rebased. Head: Same four unique commits, no content change: Ten checks green on this head. |
mixelpixx
left a comment
There was a problem hiding this comment.
Exact-head review on 35a18bc7a5a7f21bc4d8e7f393a67d3d9b974392 (four unique commits on f58b222; ten checks green; zero review threads; git range-diff identical to the head George accepted).
Local gate on this head: fmt / clippy -D warnings / --lib --tests (1758 passed, 0 failed) / --doc all exit 0; cargo xtask fix-doc-counts --check unchanged (no tool added). Neuter: the MalformedFile abort arm disabled with a false match guard → malformed_placement_aborts_a_combined_edit_without_writing fails as it should (1 of 12 fails, the rest pass); restored tree green.
Read end to end against the five corrections. Every unit refusal (non-integer, 0, u32::try_from overflow) and the malformed-file abort return before the handler's single commit_command; the readback is rebuilt from the reparsed committed file, reads (hide yes) both as a direct child and nested in (effects …), and placement_landed refuses with stale_target on mismatch. The MalformedFile / TargetMissing split is the right cut and the borderline case (a property with no (at …) at all) is on the right side of it. Twelve new tests, each pinning a named guard.
Merging on this head. Two small things for after, neither blocking and neither worth a fifth push:
- Body only: it still says "Unique commits: two" (the head has four), the neutering table lists eight of the twelve tests, and the "File mutations go through this handler's existing text-edit path" bullet appears twice. An edit to the description is enough.
- Follow-up issue, filed by me with credit to you: (a)
rotationin a placement is written verbatim, so450or-90lands raw andplacement_landedaccepts it, while the symbol-rotate path in the same file normalises withrem_euclid(360.0)— KiCad re-saves it differently than Konnect reported, the #273 class; (b) the(unitsearch inside a property block is unbounded, unlike the(at/(hidesearches you bounded past the closing quote after the first defect, so a value containing(unitwould be misread as unit 1 viaunwrap_or(1); (c) the abort error carries no structured kind, unlike its sibling refusals. Same tool, same author if you want it — it is a P2.
Closes #490 is the terminal reference. Label → status:ready-to-merge on this exact SHA.
Konnect's tool schemas are public API and API_MIGRATIONS.md is where an argument's contract is recorded; the PR body and the tool-directory row are not that durable record. Asked for on mixelpixx#489 and on mixelpixx#514's design acceptance in the same words, so this branch carries its own entry rather than waiting to be asked a third time. The entry covers what a caller needs: that `mirror` is optional on add_schematic_component and on each batch_place_components entry, that it speaks the file format's own "x"/"y"/"none", that omitting it keeps the previous behaviour exactly, why there is no both-axes spelling, that an unknown axis refuses the call (or only its own batch entry) rather than placing the symbol upright, that the field anchors follow the reflection, and that mirror_x/mirror_y are read from the reparsed committed schematic beside x, y and rotation and bound as intent, so a reflection that did not reach the file refuses with stale_target. No code change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Nothing in the server can position or hide a placed symbol's field text.
reset_schematic_field_positionsonly restores the library default, so a field that lands badly — the classic case is a rotated gate whose Value sits across its own pin numbers — has no fix inside the tooling. The way round is to empty the Value and draw the text some other way, which is a worse schematic.edit_schematic_componentgainsfield_placements, keyed by field name, each entry setting any ofx,y,rotationandhide.Issue: #490. It satisfies every acceptance criterion there; there is no follow-up half.
Closes #490
Approach
A separate argument rather than an overload of
fields: a caller moving the Value text is not changing what it says, and widening one argument's value type would make the schema lie about what it accepts. An omitted part of an entry is left as the file has it, so moving a field cannot silently unhide it.No new tool.
sch_componentsis atMAX_TOOLS_PER_TOOLSET, and that constant asks for a conversation before it moves — so this goes on the existing per-symbol field editor, which is its natural home anyway. No registry,tool-directory.mdor doc-count changes.Coordinates are absolute schematic millimetres, as KiCad stores them — not offsets from the body. That makes them per-placement, so
unitnames the one meant, and a position given for a component with several placed units and no unit named is refused. Writing one coordinate to each would stack a quad gate's four Values on a point and report success.hidewrites(hide yes)as a direct child of the property. That is not the form KiCad's own writer uses on a placement — it nests the flag in(effects …)there, and uses the direct child inlib_symbols. The two are equivalent to KiCad 10.0.6: a file in each form exports byte-identical SVG, and its writer round-trips this one unchanged. The readback accepts either, so a field KiCad itself hid reads as hidden rather than visible.The readback reports each field's observed position and visibility, and every requested placement is compared against the committed file before the call reports success. That check earns its place — see Validation.
Branch and dependencies
Base branch:
main, rebased ontof58b222. Depends on: nothing. Independent of #485 (schematic mirror), which touches the same file but not the same code; they merge cleanly in either order. Unique commits: four — the feature, the fix for what adversarial review found in it, and the two corrections from maintainer review: refusing a malformed existing placement, then aborting the whole call when the file's placement is malformed.Compatibility and safety
The public contract, in full
Everything added is additive; nothing is renamed, removed, or changed in meaning.
Request — two new optional properties on
edit_schematic_component:field_placementsx(number),y(number),rotation(number),hide(boolean). An omitted member leaves that aspect as the file has it.unitfield_placementsapplies to. Required whenxoryis given and the component has more than one placed unit.Response — one new field on each unit of the component mutation readback:
units[].field_placements{ "x": number, "y": number, "rotation": number, "hide": boolean }as observed in the committed file, not as requested. Present for every property carrying an(at …).Omitting both request fields reproduces the previous behaviour exactly, and the new response field is added alongside the existing ones rather than replacing any.
commit_command; the only new writes are a property's own(at …)and its(hide yes)token.One limitation worth stating plainly, and it is pre-existing — now filed as #499. This handler commits and then reads back, so a refusal still leaves the file changed. Both bugs found below returned
stale_targetover an already-written file. The value-edit path and two other handlers share that shape, so this change does not introduce it — but "the tool errored" cannot be read as "the file is untouched". #499 names the shared path (commit_commandand its threesch_components.rscallers) and states the atomic and accurately-reported outcomes; it is not implemented here, and this change adds no new unsafe path of its own.Validation
Run on the rebased head. Tests were also run with
HOMEandKICAD10_SYMBOL_DIRpointed at an empty directory, so nothing here depends on an installed KiCad.Every guard was neutered and watched to fail — twelve tests, each pinning one named guard:
(at …)searchfield_placement_never_rewrites_a_value_that_looks_like_a_placementhidedeletionfield_placement_hide_removal_keeps_a_neighbour_on_the_same_linefield_placement_refuses_a_position_across_units_without_one_namedfield_placement_refuses_a_non_numeric_coordinate_without_writing(at …)editfield_placement_moves_the_text_and_keeps_its_valuehideinsertfield_placement_hides_then_shows_a_fieldplacement_landed_compares_only_what_was_requestedfield_placement_reads_the_hidden_flag_kicad_nests_in_effectsfield_placement_targets_one_named_unit(at …)field_placement_refuses_a_malformed_existing_placement_without_writingMalformedFileabort armmalformed_placement_aborts_a_combined_edit_without_writingu32::try_fromonunitan_oversized_unit_is_refused_rather_than_wrapping_to_a_real_unitThree defects were found after the feature already worked and passed its first tests — two by adversarial review of the writer, one by checking the readback against what KiCad actually writes. All are fixed in the second commit, and each is worth naming because none was caught by a test:
(atwas rewritten instead of the placement. A value of"mounted (at 45 deg)"matched the search first, and moving that field turned its text into"mounted (at 11 22 0)". Both searches now start after the value's closing quote.hidedeleted back to the preceding newline, taking whatever shared the line. On a file written as(at 1 2 0) (hide yes)— not KiCad's own layout, but one it is asked to read — unhiding a field destroyed its position.lib_symbolsform. Every field KiCad itself had hidden was reported visible, and because the writer's check is textual and does see the nested token, asking to hide such a field became a no-op that then failed its own post-write comparison. Found by reading what KiCad writes, not by a test: every other test drives a field this fixture leaves visible.The first two each produced a file that parses, loads in KiCad and exports a netlist, and is wrong; both were caught by the post-write comparison against the committed file, which is the argument for having it. The third made that same comparison refuse a correct file. Each now has a regression test.
Three tests of mine passed while neutered on the first attempt and were rewritten rather than kept: one accepted any error from a handler with several failure paths, one counted a substring after an
unwrap_or(0)that had quietly made the whole file the search window, and one compared two placements against a distance the 1.27 mm grid snap had already changed.Verified against real KiCad (10.0.6), not only in-process: a field moved to
(at 70 75 90)and a(hide yes)written and removed, thenkicad-cli sch export netlistloading the result andkicad-cli sch upgrade --force— KiCad's own writer — round-tripping the moved position and the property-level hide token unchanged. The two hide forms were also rendered against each other:kicad-cli sch export svgfrom a file carrying the property-level token and one carrying(effects … (hide yes))produces byte-identical output, which is why the writer keeps one form and the readback accepts both.Not run: Windows and Linux; no environment here. Nothing in the change is platform-dependent — it is byte-range edits in the schematic writer — and hosted CI covers the matrix.
Review checklist
upstream/mainand has no merge conflicts.upstream/main, not a release tag.docs/NAMING_CONVENTIONS.md; no public renames.