Repository navigation
20261014 - Group the Configuration page around how an owner uses a node - #119
Conversation
The Configuration page had grown a section at a time, and its order no longer matched how an owner works through a node. Node Claim sat near the bottom of a long Administration group, the centre frequency lived in Capture while the tower it belongs to was two sections up, and the side menu's group headings looked the same as the links under them. The page is now five groups: Node (Identifiers, Mode, Node Claim, Network), Radar (Cached Towers, Tower, Capture, Tracking), ADS-B (ADS-B Truth, tar1090), Other (SSH Access, Setup Wizard) and Support (Cloud Services, Remote Support, How We Reach You). Each group is titled in the page itself with a rule running out to the right, the side menu's group titles carry the same rule, and a section has one name, written the same in the menu, in its heading and in the wizard hints that point at it. What was "Tower" is "Cached Towers" and what was "Location" is "Tower". Center Frequency moves into Tower's transmitter block, after Name, because it is a property of the tower and not of the capture. Tower shows the receiver's name read-only; it is changed under Identifiers, which also shows the server's identifier for the node beside the local one. The form Apply changes submits now wraps exactly the Radar and ADS-B groups. That is the property the regrouping must not break: a radar field left outside the form is missing from the POST, which the server reads as that setting having been cleared. tests/test_config_layout.py is new and holds the order, the group titles, the form's membership and the centre frequency's new home. test_app.py, test_claim.py, test_contact.py and test_node_name.py follow the renamed sections. Docs: config-editor.md gains a Page layout section and uses the new names throughout; setup-wizard.md, device-state-and-telemetry.md and remote-access.md follow the renames. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AoHZhvWU7Ge89Cm6YgrExB
Purple10101
left a comment
There was a problem hiding this comment.
I tested this on a node with an RSPduo (blah2 v0.7.0, retina-tracker v0.5.0). The regroup works:
- All 27 editable fields round-trip through the new form into
config.yml, Center Frequency from Tower included, and the radar restarts on them. - Mode switches through Apply both ways.
- Validation errors land in the right sections.
- The Cached Towers picker fills Tower.
- SSH Access, Identifiers and the side menu all work.
Three things need changing before this merges.
1. The receiver name. The hidden location.rx_name always posts node_name or node_id, and on the test node that causes two problems:
- Every Apply overwrites it. After a rename to "Sample Rooftop" and an Apply,
config.ymlhadrx.name: Sample Rooftop. Running the wizard's tower step (/towers/select) then wrote the node ID back, so Home's subtitle flips between the two while the node keeps its name. A receiver name an owner set before this PR is also silently replaced on their next Apply, with no sign of it on the page. - Numbers and true/false change type.
parse_flat_form_datacoerces the hidden value: a node named1234was stored as the integer1234inuser.ymlandconfig.yml, andtrueas a boolean, which Home shows as "True". On pydantic v2 the same name makes every Apply fail validation.
Please pick one rule, the node name falling back to the node ID. Apply it in both /config/save and /towers/select, ideally server-side rather than from a posted field, and keep it a string. config-editor.md#receiver-name ("Nothing reads that copy") needs updating, since Home reads it.
2. Remove the Minimum SNR field. retina-tracker v0.5.0 no longer reads min_snr (ed9bbea, "Track every detection: no per-site SNR threshold"), so the Tracking section offers a setting that does nothing. Please remove the field and its now-empty section, along with its side menu link, tests and docs. Once the field is gone, the next save drops retina_tracker from user.yml. That's harmless, but worth saying in the commit.
3. Smaller things:
- A rename sets
rxNameValuewithout aninputevent, so the unsaved-changes pill neither lights nor counts it. - The comment at
config.html:1135still says "Location shows the same name". config-editor.mdsays "Everything that is not a radar field saves on its own endpoint", but Mode goes through Apply.test_config_layout.pydoesn't assert there's no nested<form>inside#configForm, and its "every group" save test posts nolocation.*ortruth.*fields.
Review of the Configuration regroup found the receiver name had two writers that disagreed. The page posted location.rx_name in a hidden field as "node name or node id", while the wizard's tower step wrote the node id unconditionally, so an Apply wrote the owner's name and then running the tower step wrote the id back over it. Home's subtitle is drawn from that copy, so it flipped between the two while the node kept its name. Worse, the posted copy went through parse_flat_form_data, which types values by their shape: a node named 1234 reached user.yml and config.yml as the integer 1234, and one named true as a boolean, which Home renders as "True". On pydantic v2 the same name fails LocationFormConfig on every Apply. There is now one rule, node_name.receiver_name: the node's name, falling back to the node id, always a string. Both writers call it and neither takes the value from the request, so a crafted POST cannot set a different name. The Tower section still shows the name but no longer posts it, which also means a rename no longer stages a phantom unsaved change on the page. A receiver name an owner set by hand before this rule existed is replaced the next time either endpoint writes the location. Minimum SNR is gone with its section, its side-nav link and its scrollspy entry. retina-tracker stopped reading min_snr in v0.5.0 (ed9bbea, "Track every detection: no per-site SNR threshold"), so the field tuned nothing. RetinaTrackerConfig and the retina_tracker. form prefix go with it, and parse_flat_form_data returns four sections instead of five. /config/save still drops a retina_tracker key when it rebuilds user.yml, so a node that carries a stale block is cleaned up by its next save even though nothing writes one back; the tracker loads and ignores such a key in the meantime, so nothing breaks before that save. The setup wizard's Auto-Calibrate step told owners that tuning Minimum SNR to their noise floor would improve calibration. That advice now points at a control that does not exist, so the paragraph is gone too. Also corrected two things the review caught in passing: the comment by the rename handler still said Location posts the same name, and config-editor.md claimed everything that is not a radar field saves on its own endpoint, which Mode contradicts by staging its switch into the Apply changes flow. Tests: test_config_layout.py gains the nested-form assertion the layout tests were missing, and its every-group save now posts location.* and truth.* rather than only capture and tar1090. New tests cover the name being written from the node rather than the form, a numeric name staying a string, a posted name being ignored, the wizard writing the name rather than the id, and a stale retina_tracker block being dropped. Full suite 1071 passed; the one failure, test_an_unwritable_location_reports_rather_than_raises, fails only because the suite ran as root, where an unwritable directory is still writable. Docs: config-editor.md's Receiver name section is rewritten and no longer says nothing reads the copy, because Home does; a No tracking section records why Tracking went and what happens to a stale key; the form-generation and parsing sections lose retina_tracker; the administration-sections claim now names Mode as the exception. towers.md no longer says rx_name is always the node id. After this: retina-node's default.yml still sets min_snr (retina-tracker docs/measurement-noise.md lists it as the other outstanding follow-up). It is harmless, since the tracker ignores it, but it means the node still ships a dead key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bhhab26X2mbjmbtq7wKdUf
Purple10101
left a comment
There was a problem hiding this comment.
Thanks, all three points are addressed, and I've re-tested on the node:
- A numeric or true/false name now stays a string in both config files.
- A posted
location.rx_nameis ignored, and so is one slipped into/towers/select. The wizard now writes the node's name, so Home no longer flips. - An unnamed node falls back to its ID.
- Minimum SNR is gone, and a stale
retina_trackerblock is dropped fromuser.ymlon the next save. - All 26 remaining fields still round-trip, and validation errors still land in the right sections.
Merged with current main, the suite passes (1081). Approving.
Review of #120 found three. None of them is in the drawing, which is most of the code; all three are in the state around a run. The last step of the setup wizard offered Start tutorial and Go to dashboard from the moment it rendered, while the two requests that end the wizard were still in flight with their failures swallowed. save-step with complete is the one that clears the step file and writes the completion flag, and until it lands is_setup_wizard_in_progress is still true, so home.index, the config blueprint and tutorial.start all redirect back to /set-up. An owner who clicked Start tutorial too quickly, or at all if the save had failed, was sent back into the wizard they had just finished. showStep now skips the generic save for the last step, and enterHooks.complete calls finishSetup, which awaits that one save and reveals the two links only once it has returned. A failure shows the server's reason and a Try again button rather than nothing, and costs nothing else: the step file still holds the previous step, so a reload resumes there. /set-up/complete is still posted without being awaited, because it brings the radar stack up, takes tens of seconds, never raises, and gates nothing on the page. Second, a run could be renumbered while it was open. A step is addressed by its number in the run, and the run is 28 steps long when Summary is toured and 23 when it is not, so the number means nothing without the list. _include_summary asked discovered_nodes on every request, which meant a peer appearing or dropping mid-tour swapped the list underneath the owner: the same ?tutorial=6 stopped being Home's first step and became Configuration's welcome. The question is now asked once, by start, and the answer travels in the URL as summary=1 the way demo=1 already does, with demo implying it. A link carrying a number but no shape is read as a single node's run, and payload already renders the plain page when the number does not name a step there. Third, Skip did not stick. Each page of a tour was a pushed history entry carrying ?tutorial=, and close cleans only the entry it is standing on, so one press of Back brought the tour straight back. Moves within a run now use location.replace, so a whole run occupies the single entry it started in: there is one entry to clean and Back leaves the tour rather than walking it backwards. Nothing records the dismissal and nothing needs to, because Back now lands on the page from before the tour, which carries no parameter. Tests: test_tutorial.py gains a peer appearing and a peer dropping mid-run, both asserting the payload is unchanged; that every step URL carries the shape; that a demo link works carrying only demo=1; that the script holds no location.assign; and that the wizard's last step starts with both links hidden, offers a retry, and does not fire-and-forget the completion save. Four existing tests now pass summary=1 where they relied on the node count being re-read. On the rebased stack, which is how this lands, the suite is 1105 passed with ruff clean and one failure, test_an_unwritable_location_reports_rather_than_raises, which fails only because the suite ran as root, where an unwritable directory is still writable. On this branch before the rebase it is 1101 passed and that failure plus test_no_token_is_left_without_a_step, which finds cfg-tracking orphaned because #119 is what removes the section the dropped step pointed at. Not run in a browser or on a node. The three fixes are all browser behaviour at heart, so the Tutorial and Setup wizard tabs of the manual test plan are the real verification. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bhhab26X2mbjmbtq7wKdUf
retina-tracker v0.5.0 tracks every detection and has no per-site SNR gate (ed9bbea, "Track every detection: no per-site SNR threshold"), which left the below_snr class permanently empty while the Tracker page still offered it as a layer worth trying and described it as detections dropped at the SNR gate before the tracker considered them. An owner who picked it saw an empty scope with no way to tell that from a quiet sky. Only the button goes. The class is still read from the stream, still counted against the untracked cap and still drawn under All, so a node running an older tracker shows those detections with the rest rather than behind a button of their own. A layer remembered in localStorage falls back to All unless the page still offers it, so neither an owner who had Below SNR selected nor a hand-edited value can select a layer that no longer exists. This was split out of the Configuration review fixes: the rest of that work merged in #119, and this is the part of it that had not landed. Tests: test_tracker_routes.py follows the removed button. Docs: tracker.md's detection-class table moves below_snr to "All only", stops saying the tracker drops anything at an SNR gate, and records why the button went and what still happens to the class. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wh14qma4dunvECxn7Wj4YW
Re-opened from #115 under the author's own GitHub account, as the reviewer asked; same code (commit 3206489 on #113, rebased onto current
main). 1 of the 4 PRs split out of #113; #116 and #117 are already merged.What
The Configuration page is regrouped around how an owner works through a node: Node (Identifiers, Mode, Node Claim, Network), Radar (Cached Towers, Tower, Capture, Tracking), ADS-B (ADS-B Truth, tar1090), Other (SSH Access, Setup Wizard) and Support (Cloud Services, Remote Support, How We Reach You).
Why
The page had grown a section at a time and its order no longer matched how a node is set up and tuned: Node Claim sat near the bottom of a long Administration group, the centre frequency lived in Capture while the tower it belongs to was two sections up, and the side menu's group headings looked the same as the links under them. The guided tutorial (coming as a separate PR) walks this page top to bottom, so the order had to make sense first.
The change
tests/test_config_layout.pyholds this.Testing
tests/test_node_name.py::test_an_unwritable_location_reports_rather_than_raises, which fails in the sandbox the suite ran in because it runs as root there, and fails the same way on untouchedmain.ruff check .and the vulture hook pass.tests/test_config_layout.py.test_app.py,test_claim.py,test_contact.pyandtest_node_name.pyfollow the renamed sections.mainhas not been on hardware.Docs
docs/features/config-editor.md(new Page layout section, renamed sections),docs/features/setup-wizard.md,docs/features/device-state-and-telemetry.mdanddocs/features/remote-access.md(renamed sections).Review fixes (20261019)
Receiver name.
location.rx.namehad two writers that disagreed: the page posted it from ahidden field as "node name or node id", while
/towers/selectwrote the node id unconditionally,so running the wizard's tower step wrote the id back over a name an Apply had just written. Home's
subtitle is drawn from that copy, so it flipped between the two. The posted copy also went through
parse_flat_form_data, which types values by shape: a node named1234was stored as an integerand
trueas a boolean, and on pydantic v2 such a name failsLocationFormConfigon every Apply.There is now one rule —
node_name.receiver_name, the node's name falling back to the node id,always a string — called by both writers and never read from the request. The Tower section shows
the name and posts no copy of it.
Minimum SNR removed. retina-tracker stopped reading
min_snrin v0.5.0 (ed9bbea), so thefield tuned nothing. Gone with its section, side-nav link, scrollspy entry,
RetinaTrackerConfigand the
retina_tracker.form prefix. The setup wizard's Auto-Calibrate step also advised tuningit; that paragraph is gone too.
/config/savestill drops aretina_trackerkey when it rebuildsuser.yml, so a node carrying a stale block is cleaned up by its next save; the tracker loads andignores such a key in the meantime.
Smaller things. The comment by the rename handler no longer claims Location posts the same
name.
config-editor.mdno longer claims everything that is not a radar field saves on its ownendpoint, which Mode contradicts.
test_config_layout.pygains the nested-<form>assertion itwas missing, and its every-group save now posts
location.*andtruth.*rather than onlycapture and tar1090.
Testing
Full suite 1071 passed, ruff clean.
test_an_unwritable_location_reports_rather_than_raisesfails only when the suite runs as root, on this branch and on
mainalike. Not run in a browserand not tried on hardware.
Docs
config-editor.md: Receiver name rewritten — it no longer says nothing reads the copy, becauseHome does; new "No tracking section"; form-generation and parsing sections lose
retina_tracker;the administration-sections claim now names Mode as the exception.
towers.mdno longer saysrx_nameis always the node id.After this
retina-node'sdefault.ymlstill setsmin_snr. retina-tracker's owndocs/measurement-noise.mdlists it as the other outstanding follow-up. It is harmless, since thetracker ignores it, but the node still ships a dead key.
https://claude.ai/code/session_01Wh14qma4dunvECxn7Wj4YW
Generated by Claude Code