Skip to content

test(profiles): pin profile chip↔dropdown source-of-truth invariant (#3635, stacked on #3637) - #3639

Closed
nesquena-hermes wants to merge 1 commit into
fix/3635-profile-chip-activefrom
test/3635-profile-source-of-truth-invariant
Closed

test(profiles): pin profile chip↔dropdown source-of-truth invariant (#3635, stacked on #3637)#3639
nesquena-hermes wants to merge 1 commit into
fix/3635-profile-chip-activefrom
test/3635-profile-source-of-truth-invariant

Conversation

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #3637 (base = fix/3635-profile-chip-active). This PR is test-only — no production code — and should merge after #3637. Once #3637 merges to master, GitHub will retarget this to master automatically; the diff is purely the new test.

Adds a standing source-of-truth invariant test that generalizes the #3635 fix so the regression can't silently come back.

Why

#3635 was a UI source-of-truth divergence: the composer profile chip (syncTopbar() in ui.js) and the profile dropdown's active/checkmark row (renderProfileDropdown() in panels.js) are two renderings of the same fact — which profile is active — but #3331 pointed the chip at S.session.profile while the dropdown kept reading S.activeProfile. Opening a cross-profile session made the switcher trigger disagree with the menu it opens, and misrepresented where the next message would route.

#3637 fixes the chip. This PR pins the invariant so a future change can't re-split the two surfaces:

What

tests/test_issue3635_profile_chip_active.py gains a TestProfileSwitcherSourceOfTruthInvariant class (deterministic JS-source assertions, no browser, runs in the normal pytest suite):

  • test_chip_keys_on_active_profile — the chip (ui.js syncTopbar) resolves from S.activeProfile, never S.session.profile
  • test_dropdown_active_row_keys_on_active_profile — the dropdown active row (panels.js renderProfileDropdown) resolves from S.activeProfile
  • test_chip_and_dropdown_share_source_of_truth — cross-file check that both key on S.activeProfile

Testing

Refs #3635

…#3635)

Adds a standing invariant guard generalizing the #3635 fix: the profile chip
(syncTopbar/ui.js) and the profile dropdown's active/checkmark row
(renderProfileDropdown/panels.js) are two renderings of 'which profile is
active' and must resolve from the same source of truth (S.activeProfile).

#3331 split them — the chip was pointed at S.session.profile while the dropdown
kept reading S.activeProfile — so opening a cross-profile session made the
switcher trigger disagree with the menu it opens (#3635). A purely additive,
deterministic test (no browser, runs in the normal pytest suite) that fails the
instant the two surfaces key on different variables again:

- test_chip_keys_on_active_profile — chip reads S.activeProfile (ui.js)
- test_dropdown_active_row_keys_on_active_profile — dropdown active row reads
  S.activeProfile (panels.js)
- test_chip_and_dropdown_share_source_of_truth — cross-file: both agree

Stacked on the #3635 fix (PR #3637). No production code change.

Refs #3635
@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR is test-only, stacked on #3637, and adds TestProfileSwitcherSourceOfTruthInvariant to pin the source-of-truth invariant introduced by the #3635 fix — ensuring the profile chip (syncTopbar/ui.js) and profile dropdown active row (renderProfileDropdown/panels.js) always resolve from the same value, S.activeProfile.

  • test_chip_keys_on_active_profile — regex-scans the extracted syncTopbar() body for every profileChipLabel.textContent= assignment and asserts each reads S.activeProfile, not S.session.profile.
  • test_dropdown_active_row_keys_on_active_profile — extracts renderProfileDropdown() from panels.js and asserts const active = ... resolves from S.activeProfile.
  • test_chip_and_dropdown_share_source_of_truth — cross-file check asserting both surfaces key on S.activeProfile; the chip-side positive check (\"S.activeProfile\" in chip_body) is trivially satisfied by inline code comments in syncTopbar(), weakening the guarantee it claims to provide.

Confidence Score: 3/5

The PR is test-only with no production changes, but the cross-file invariant test's chip-side assertion is satisfied by comment text rather than the actual assignment expression, so it could silently pass even if the chip regresses.

The cross-file invariant test's chip-side positive check is satisfied by comment text in the function body rather than the actual assignment expression, so it could silently pass even if the chip regresses to a different source of truth. The focused per-surface tests do anchor to actual assignment expressions and provide real coverage, but the test that claims to be the standing invariant has a meaningful logical gap on the chip side.

tests/test_issue3635_profile_chip_active.py — specifically lines 157-158 (chip_ok logic) and line 129 (120-char regex gap).

Important Files Changed

Filename Overview
tests/test_issue3635_profile_chip_active.py Adds TestProfileSwitcherSourceOfTruthInvariant with three invariant tests; the cross-file test's chip-side positive check is satisfied by comment text rather than the actual assignment expression, and the assignment-locating regex has a 120-char gap limit that could produce misleading failures on reformatting.

Reviews (1): Last reviewed commit: "test(profiles): pin profile chip<->dropd..." | Re-trigger Greptile

Comment on lines +157 to +158
chip_ok = "S.activeProfile" in chip_body and \
"(S.session&&S.session.profile)||S.activeProfile" not in chip_body

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 chip_ok positive check is satisfied by comment text, not the assignment

"S.activeProfile" in chip_body is trivially True on the current code because syncTopbar() contains inline comments (lines ~5907-5908 of ui.js) that spell out S.activeProfile in explanatory prose — e.g., // It must therefore reflect S.activeProfile, NOT the loaded session's profile. The substring match hits the comment before it ever reaches the actual assignment expression. If a future change re-points the chip to S.session?.profile || S.activeProfile (or any novel form that doesn't match the negative pattern), chip_ok remains True and the test passes silently, exactly contradicting the docstring that says "the switcher trigger and the menu it opens can never again silently disagree."

The two focused tests (test_chip_keys_on_active_profile with its findall regex, and test_both_chip_setters_consistent) already pin the actual assignment expressions and would catch this class of regression — but test_chip_and_dropdown_share_source_of_truth overstates its own guarantee on the chip side. Consider anchoring chip_ok to the same regex-based assignment extraction used in test_chip_keys_on_active_profile rather than a bare in chip_body substring test.

Comment on lines +129 to +135
assignments = re.findall(r"profileChipLabel'\);[\s\S]{0,120}?\.textContent=([^;]+);", body)
assert assignments, "no profileChipLabel assignment found in syncTopbar()"
for expr in assignments:
assert "S.activeProfile" in expr and "S.session.profile" not in expr, (
"profile chip (switcher trigger) must resolve from S.activeProfile, "
"not the loaded session's profile: " + expr.strip()
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 120-char window in the regex could produce a misleading failure on reformatting

profileChipLabel'\);[\s\S]{0,120}?\.textContent= caps the gap between the $('profileChipLabel') call and the .textContent= assignment at 120 characters. Right now the gap is roughly 35 characters (\nif(_profileLabel) _profileLabel), so it works. But if someone adds an inline guard, splits the if-condition, or inserts a short comment between the two statements, re.findall could return [] — causing the assertion assert assignments to fire with "no profileChipLabel assignment found in syncTopbar()" rather than any message about source-of-truth violations. The failure diagnostic would be actively misleading.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator Author

Superseded by #3644 — the rebased, test-only version. #3639's branch was stacked on the pre-squash #3637 and would have reverted ~5 shipped releases (IF/IG/IH) if merged as-is; #3644 carries only the +74-line invariant test, merged clean to master. Thanks!

nesquena-hermes added a commit that referenced this pull request Jun 5, 2026
…from #3639) (#3644)

Test-only. Adds TestProfileSwitcherSourceOfTruthInvariant generalizing the #3635
fix so the chip + dropdown can't re-split their source of truth (both must read
S.activeProfile). Rebased onto current master — the original #3639 branch was
stacked on the pre-squash #3637 and would have reverted ~5 shipped releases
(IF/IG/IH) if merged as-is; this carries ONLY the +74-line test delta.

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: nesquena <nesquena@users.noreply.github.com>
jja881 pushed a commit to jja881/hermes-webui that referenced this pull request Jun 5, 2026
…rebased from nesquena#3639) (nesquena#3644)

Test-only. Adds TestProfileSwitcherSourceOfTruthInvariant generalizing the nesquena#3635
fix so the chip + dropdown can't re-split their source of truth (both must read
S.activeProfile). Rebased onto current master — the original nesquena#3639 branch was
stacked on the pre-squash nesquena#3637 and would have reverted ~5 shipped releases
(IF/IG/IH) if merged as-is; this carries ONLY the +74-line test delta.

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: nesquena <nesquena@users.noreply.github.com>
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…rebased from nesquena#3639) (nesquena#3644)

Test-only. Adds TestProfileSwitcherSourceOfTruthInvariant generalizing the nesquena#3635
fix so the chip + dropdown can't re-split their source of truth (both must read
S.activeProfile). Rebased onto current master — the original nesquena#3639 branch was
stacked on the pre-squash nesquena#3637 and would have reverted ~5 shipped releases
(IF/IG/IH) if merged as-is; this carries ONLY the +74-line test delta.

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: nesquena <nesquena@users.noreply.github.com>
@nesquena-hermes
nesquena-hermes deleted the test/3635-profile-source-of-truth-invariant branch June 28, 2026 06:14
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.

1 participant