Skip to content

Spell the permanent link the way the page reads it back - #114

Merged
openipc-ai merged 2 commits into
masterfrom
permalink-round-trip
Aug 25, 2026
Merged

Spell the permanent link the way the page reads it back#114
openipc-ai merged 2 commits into
masterfrom
permalink-round-trip

Conversation

@openipc-ai

Copy link
Copy Markdown
Collaborator

Closes #111.

Camera#permalink wrote &var=; Cameras::SocsController#show has only ever
read params[:ver]. The edition was the one field the permanent link dropped —
pick Ultimate on a 32MB chip, follow your own link, and the wizard comes back on
Lite with the flash type intact and nothing to say it changed. mac, cip,
sip, net, rom and sd all round-tripped, which is why it went unnoticed.

permalink now emits ver. show still accepts var, because the old
spelling is the only one any already-shared link carries; ver wins if a link
somehow has both.

The reader is a table now

The seven near-identical x = params[:k] if params[:k] lines are one frozen
hash. The defect was a key disagreeing with a field, and a table is where that
disagreement is legible; insertion order carries the var-then-ver
precedence. The MAC stays out of it — unlike the rest it is rewritten rather
than copied, and applied whether or not the link carried one.

That also takes show below two thresholds it had been over:

before after
Metrics/AbcSize (show) 52.86/17
Metrics/CyclomaticComplexity (show) 8/7
Metrics/MethodLength (show) 20/10 14/10

42 rubocop offences in the touched files, down from 44. No new categories.

Verification

bin/rails test — 196 runs, 742 assertions, 0 failures (192 before).

The two model tests pin the whole key set against the reader rather than the
one key that broke, so the next field to drift fails here rather than in
someone's shared link.

revert fails
permalink back to &var= 2 (both model tests)
var dropped from the table 1 (shared-link test)

🤖 Generated with Claude Code

https://claude.ai/code/session_01YFg9PRy2T6cr983S8gran5

Camera#permalink wrote `&var=` and Cameras::SocsController#show has only
ever read `params[:ver]`, so the edition was the single field the
permanent link dropped. A visitor who picked Ultimate on a 32MB chip and
followed their own link -- or sent it to someone else, which is what the
link is for -- got the wizard back on Lite, with the flash type intact
and no sign anything had changed. Every other field round-tripped, which
is why it went unnoticed.

permalink now emits `ver`. `show` accepts `var` as well, because the old
spelling is the only one any shared link carries; `ver` wins where a link
somehow has both.

The seven near-identical reader lines are now one table. The bug was a
key disagreeing with a field, and a table is where that disagreement is
visible; insertion order carries the precedence. Pulling them out of
`show` also drops that action below the Metrics/AbcSize and
Metrics/CyclomaticComplexity thresholds it had been over, and its length
from 20 lines to 14 -- two fewer rubocop offences in the touched files
than before this change.

Four tests. The two model tests pin the whole key set against the reader
rather than the one key that broke, so the next field to drift fails
here. Reverting permalink to `var` fails both; dropping `var` from the
table fails the shared-link test.

Closes #111.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFg9PRy2T6cr983S8gran5
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix permanent-link edition round trips with legacy compatibility

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Emit the edition key consumed when reopening permanent links.
• Preserve compatibility with legacy links while preferring the corrected key.
• Cover complete query serialization and restored wizard configurations.
Diagram

sequenceDiagram
  actor User
  participant Camera as Camera Model
  participant Link as Permanent Link
  participant Controller as SoCs Controller
  participant Wizard
  User->>Camera: Select configuration
  Camera->>Link: Serialize with ver
  User->>Controller: Open shared link
  Controller->>Controller: Apply var then ver
  Controller->>Wizard: Render restored values
  Wizard-->>User: Show original edition
Loading
High-Level Assessment

The chosen approach is appropriate: correct the writer to match the established ver reader contract, retain var only for backward compatibility, and centralize mappings so future key drift is visible. Removing legacy support would break shared links, while continuing to emit var would preserve the inconsistent contract.

Files changed (4) +104 / -8

Bug fix (2) +31 / -8
socs_controller.rbCentralize permanent-link parsing with legacy edition support +26/-7

Centralize permanent-link parsing with legacy edition support

• Replaces repeated query-parameter assignments with a frozen key-to-field mapping. Accepts legacy 'var' links, applies corrected 'ver' afterward for precedence, and preserves separate MAC normalization.

app/controllers/cameras/socs_controller.rb

camera.rbSerialize firmware editions under the corrected ver key +5/-1

Serialize firmware editions under the corrected ver key

• Changes permanent-link generation from 'var' to 'ver', matching the controller's established query contract so firmware editions survive round trips.

app/models/camera.rb

Tests (2) +73 / -0
socs_controller_test.rbVerify current and legacy links restore wizard selections +43/-0

Verify current and legacy links restore wizard selections

• Adds integration coverage proving generated links reopen the selected flash type and edition. Also verifies previously shared 'var' links continue restoring their edition.

test/controllers/socs_controller_test.rb

camera_test.rbPin permanent-link keys and serialized values +30/-0

Pin permanent-link keys and serialized values

• Adds model tests for the complete permanent-link key set and value map, including MAC formatting and the corrected 'ver' edition parameter.

test/models/camera_test.rb

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Round-trip omits net and sd 📎 Requirement gap ≡ Correctness
Description
The controller round-trip helper verifies MAC, both IPs, flash type, and edition, but never verifies
that network_interface (net) or sd_card_slot (sd) survive show. The model-only query
assertions do not satisfy the required end-to-end coverage through the controller.
Code

test/controllers/socs_controller_test.rb[R567-568]

+    assert_match %(<option selected="selected" value="#{chip}">), response.body
+    assert_match %(<option selected="selected" value="#{edition}">), response.body
Evidence
PR Compliance ID 2 requires a test that processes the permalink through show and verifies mac,
cip, sip, net, rom, ver, and sd. The added controller helper asserts only MAC, the two
IP addresses, flash type, and edition; its generated request does carry net and sd, but no
post-show assertion checks either value.

Test complete permanent-link configuration round-trip
test/controllers/socs_controller_test.rb[556-578]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The permanent-link controller test does not verify that `net` and `sd` are restored by `Cameras::SocsController#show`.
## Issue Context
Compliance requires one automated round-trip through `show` that validates all seven supported fields. The current model tests only validate permalink serialization, while the controller helper omits `network_interface` and `sd_card_slot`.
## Fix Focus Areas
- test/controllers/socs_controller_test.rb[556-578]
- app/views/cameras/socs/show.html.erb[71-87]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Permalinks preserve 8MB Ultimate ✓ Resolved 📘 Rule violation ≡ Correctness
Description
Mapping var and ver directly to firmware_version lets an 8MB permalink render Ultimate
selected because show never applies the server-side 8MB edition guard. Client-side code usually
changes it to Lite, but deliberately retains Ultimate when no Lite release exists, so the
configuration is not always coerced as required.
Code

app/controllers/cameras/socs_controller.rb[R244-246]

+    PERMALINK_FIELDS = { cip: :camera_ip_address, sip: :server_ip_address, rom: :flash_type,
+                         var: :firmware_version, ver: :firmware_version,
+                         net: :network_interface, sd: :sd_card_slot }.freeze
Evidence
PR Compliance ID 13 requires every 8MB configuration to select or coerce Lite. The added permalink
table assigns both edition keys without considering flash size; show only validates the flash
type, while the firmware dropdown renders the assigned value and the JavaScript fallback can retain
Ultimate when Lite is unavailable.

CLAUDE.md: Force Lite Firmware Edition for 8MB Flash Devices: CLAUDE.md: Force Lite Firmware Edition for 8MB Flash Devices: CLAUDE.md: Force Lite Firmware Edition for 8MB Flash Devices: CLAUDE.md: Force Lite Firmware Edition for 8MB Flash Devices
app/controllers/cameras/socs_controller.rb[244-255]
app/controllers/cameras/socs_controller.rb[50-76]
app/views/cameras/socs/show.html.erb[112-170]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An 8MB permalink can restore a non-Lite firmware edition in the `show` wizard.
## Issue Context
`apply_permalink_to` assigns the requested edition before the SoC is loaded, and `show` does not invoke the existing 8MB enforcement. Enforce the size constraint after assigning the SoC, and ensure configurations without a usable Lite release cannot proceed to non-Lite 8MB instructions.
## Fix Focus Areas
- app/controllers/cameras/socs_controller.rb[59-76]
- app/controllers/cameras/socs_controller.rb[244-255]
- app/controllers/cameras/socs_controller.rb[291-307]
- test/controllers/socs_controller_test.rb[571-594]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread test/controllers/socs_controller_test.rb
Comment thread app/controllers/cameras/socs_controller.rb
Qodo's review of the change: accepting `var` back made a combination
reachable that was not before. `show` had no server-side size rule --
the menu applies it in JavaScript, after this action has already decided
which options open selected -- so `?rom=nor8m&var=ultimate` rendered the
form on nor8m + Ultimate, which enforce_eight_meg_limit refuses on
submit. Confirmed by rendering it: `nor8m, ultimate`.

Silent here, unlike in `update`. There the edition decides what gets
flashed and the visitor is told when it changes; here it only decides
where a form opens. Same carve-out as enforce_eight_meg_limit: a SoC
published as Ultimate and nothing else keeps it, because opening on a
Lite tarball upstream never built is worse than the size warning
`update` will give.

Folded together with the existing flash-type guard, since both are the
same job -- bring what arrived in the query string back inside what the
menu offers. That drops `show` under Metrics/AbcSize as well: 42
offences in the touched files against 44 on master.

Qodo also asked the round-trip test to cover `net` and `sd`. It cannot,
and the reason is worth recording rather than papering over: the selects
for both are commented out at show.html.erb:86-87, so the rendered form
carries five fields, not seven. `net` and `sd` are set on the camera by
the link and then rendered nowhere and posted nowhere -- `update` falls
back to eth/nosd whatever the link said. Asserting them would need
`assigns`, which this app has no rails-controller-testing for, and would
pin a value that goes no further than that one request. The round-trip
test now sends a link carrying wifi/sd anyway, so it covers `show`
accepting them, and the helper says plainly why it stops there.

198 runs, 750 assertions. Dropping the new guard fails the 8MB test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFg9PRy2T6cr983S8gran5
@openipc-ai

Copy link
Copy Markdown
Collaborator Author

Both findings addressed in 062bc87. One fixed, one answered.

1. Permalinks preserve 8MB Ultimate — real, and introduced by this PR

Confirmed by rendering it: ?rom=nor8m&ver=ultimate returned nor8m, ultimate
as the selected options. show had no server-side size rule, because the menu
applies that rule in JavaScript — after this action has already decided which
options open selected. Accepting var back is what made the combination
reachable, so this is mine.

Fixed, and folded together with the flash-type guard that was already there,
since both do the same job: bring what arrived in the query string back inside
what the menu offers. Silent, unlike enforce_eight_meg_limit — there the
edition decides what gets flashed and the visitor is told; here it only decides
where a form opens. Same carve-out, though: a SoC published as Ultimate and
nothing else keeps it, because opening on a Lite tarball upstream never built is
worse advice than the size warning update gives.

Folding the two also took show under Metrics/AbcSize: 42 offences in the
touched files, against 44 on master.

2. Round-trip omits net and sd — cannot be asserted, and the reason matters

The rendered form carries five fields, not seven:

name="camera[camera_mac_address]", name="camera[camera_ip_address]",
name="camera[server_ip_address]", name="camera[flash_type]",
name="camera[firmware_version]"

The selects for network_interface and sd_card_slot are commented out at
show.html.erb:86-87. So those two are set on the camera by the link, rendered
nowhere, and posted nowhere — update falls back to its own eth/nosd
defaults whatever the link said. Asserting them would need assigns, which this
app has no rails-controller-testing for, and it would pin a value that goes no
further than that one request.

So the honest coverage is: the round-trip test now sends a link carrying
net=wifi&sd=sd anyway, which covers show accepting a link that names them;
camera_test.rb covers the link carrying all seven; and the helper says plainly
why it stops at five rather than looking like an oversight.

That net and sd do not survive to the next page is a real gap, but it is a
gap in the form, not in this change — fixing it means restoring those two
selects, which is a product decision and would newly route people onto the
SD-card path.

198 runs, 750 assertions, 0 failures. Dropping the new guard fails the 8MB test.

@openipc-ai
openipc-ai merged commit 5a6f9e6 into master Aug 25, 2026
2 checks passed
@openipc-ai
openipc-ai deleted the permalink-round-trip branch August 25, 2026 11:48
openipc-ai added a commit that referenced this pull request Aug 25, 2026
Regression from #114, found while triaging website bugs filed against
OpenIPC/firmware. A permanent link names every field whether or not it
has a value, so `?...&rom=nor16m&var=&sd=sd` is what a link built from a
camera with no edition chosen looks like -- and the menu produces
exactly that, because allowedEditions falls back to '' for a chip with
nothing published. OpenIPC/firmware#1912 carries a real one.

`if params[key]` treats the empty string as an answer, so the edition
became "" and the dropdown rendered with nothing selected at all.
Before #114 this was unreachable: `show` read `ver`, which permalink
never wrote, so an empty `var=` was simply ignored. Accepting `var` --
and emitting `ver` -- made both spellings live.

`present?` instead. Blank in a query string means "not specified", so
the constructor default stands, which is what it did before.

JavaScript covers for it on load, since '' is not in the allowed list
and checkFlashSize moves off it, and a browser submits the first option
of a select with nothing marked. So this never produced wrong
instructions -- but it rendered a form the visitor had to notice and
correct, on a link the site generated itself.

Two tests, one per spelling. Reverting to bare truthiness fails both.
21 rubocop offences in the touched files, unchanged from master.


Claude-Session: https://claude.ai/code/session_01YFg9PRy2T6cr983S8gran5

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

The permanent link drops the visitor's edition: it writes &var= but show reads params[:ver]

1 participant