Skip to content

20261014 - Group the Configuration page around how an owner uses a node - #119

Merged
Purple10101 merged 2 commits into
mainfrom
20261014-config-page-groups
Oct 19, 2026
Merged

Purple10101 merged 2 commits into
mainfrom
20261014-config-page-groups

Conversation

@jfremerman

@jfremerman jfremerman commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Each group is titled in the page with a rule under it; the side menu's group titles carry the same rule.
  • "Tower" is now "Cached Towers" and "Location" is now "Tower". A section has one name, written the same in the menu, its heading and the wizard hints.
  • Center Frequency moves from Capture into Tower's transmitter block, after Name.
  • Tower shows the receiver's name read-only; it is changed under Identifiers, which also shows the server's identifier for the node.
  • The form that Apply changes submits wraps exactly the Radar and ADS-B groups. A radar field left outside the form would be missing from the POST, which the server reads as that setting having been cleared, so tests/test_config_layout.py holds this.

Testing

Docs

docs/features/config-editor.md (new Page layout section, renamed sections), docs/features/setup-wizard.md, docs/features/device-state-and-telemetry.md and docs/features/remote-access.md (renamed sections).

Review fixes (20261019)

Receiver name. location.rx.name had two writers that disagreed: the page posted it from a
hidden field as "node name or node id", while /towers/select wrote 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 named 1234 was stored as an integer
and true as a boolean, and on pydantic v2 such a 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 — 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_snr in v0.5.0 (ed9bbea), so the
field tuned nothing. Gone with its section, side-nav link, scrollspy entry, RetinaTrackerConfig
and the retina_tracker. form prefix. The setup wizard's Auto-Calibrate step also advised tuning
it; that paragraph is gone too. /config/save still drops a retina_tracker key when it rebuilds
user.yml, so a node carrying a stale block is cleaned up by its next save; the tracker loads and
ignores such a key in the meantime.

Smaller things. The comment by the rename handler no longer claims Location posts the same
name. config-editor.md no longer claims everything that is not a radar field saves on its own
endpoint, which Mode contradicts. test_config_layout.py gains the nested-<form> assertion it
was missing, and its every-group save now posts location.* and truth.* rather than only
capture and tar1090.

Testing

Full suite 1071 passed, ruff clean. test_an_unwritable_location_reports_rather_than_raises
fails only when the suite runs as root, on this branch and on main alike. Not run in a browser
and not tried on hardware.

Docs

config-editor.md: Receiver name rewritten — it no longer says nothing reads the copy, because
Home does; new "No tracking section"; 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's own
docs/measurement-noise.md lists it as the other outstanding follow-up. It is harmless, since the
tracker ignores it, but the node still ships a dead key.

https://claude.ai/code/session_01Wh14qma4dunvECxn7Wj4YW


Generated by Claude Code

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 Purple10101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.yml had rx.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_data coerces the hidden value: a node named 1234 was stored as the integer 1234 in user.yml and config.yml, and true as 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 rxNameValue without an input event, so the unsaved-changes pill neither lights nor counts it.
  • The comment at config.html:1135 still says "Location shows the same name".
  • config-editor.md says "Everything that is not a radar field saves on its own endpoint", but Mode goes through Apply.
  • test_config_layout.py doesn't assert there's no nested <form> inside #configForm, and its "every group" save test posts no location.* or truth.* 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 Purple10101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_name is 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_tracker block is dropped from user.yml on 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.

@Purple10101
Purple10101 merged commit b1e4c80 into main Oct 19, 2026
4 checks passed
@Purple10101
Purple10101 deleted the 20261014-config-page-groups branch October 19, 2026 17:47
jfremerman added a commit that referenced this pull request Oct 20, 2026
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
jfremerman added a commit that referenced this pull request Oct 20, 2026
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
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.

2 participants