Skip to content

fix(connections): flush card rows and open the approval tab - #7750

Merged
iamwhatever merged 2 commits into
mainfrom
fix/connections-card-and-popup
Sep 3, 2026
Merged

fix(connections): flush card rows and open the approval tab#7750
iamwhatever merged 2 commits into
mainfrom
fix/connections-card-and-popup

Conversation

@pepmach

@pepmach pepmach commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Two findings from a manual re-test of the Connections gallery, both user-visible:

  1. Cards in a row are not flush. Provider value props differ in length — GitLab's wraps to two lines where Notion's takes one — and the description is free-flowing text with no reserved height, so each card is as tall as its own copy. A row of cards renders visibly ragged.

  2. No mint-starting click opens the provider's approval page. The intended flow is: click Connect → land on the provider's consent page, with the card's "Re-open approval" link as the recovery path for a popup the browser refused. What shipped has no window.open anywhere on the page: the minted URL only ever appears as that link, so the first open never happens and the user has to notice a link labelled as if a window had already been opened and closed.

Why it matters

The first is the launch surface's most visible polish defect — the gallery is the page the feature is named after.

The second is the connect flow's first step. A user who does not notice the link concludes the click did nothing, and the label actively misleads: "Re-open approval" describes reopening something that was never opened. The card's own source comment recorded the gap ("A minted URL opened no tab…") rather than closing it.

What changed (motivation → approach → change)

Card height. Ragged rows → each card sizes to its own description → the description reserves a two-line floor with a three-line ceiling (min-h-[34px] line-clamp-3).

Two lines is what every English value prop needs at most — the longest, GitLab's, is 83 characters and takes exactly two — so reserving them makes the row flush. The ceiling is three rather than a fixed two because pinning it would clip, and the clipped tail is the consequential part: GitLab's copy ends in "this grant can also write", and translations run materially longer (Italian 97 characters; Russian Notion 78 against English 58). At the three-column width a verbose locale could therefore hide a write-scope disclosure behind a hover-only title that touch and keyboard users cannot reach. Growing one card by a line is the cheaper failure. The agents gallery card pins an exact height because no permission scope rides in its text.

The grid keeps items-start: a card expanded into the approval flow must not drag its row partner up to its height, which is why this is fixed on the content rather than by stretching the cards.

The approval tab. The naive fix (window.open(url) after the mint) does not work, and the reason is the endpoint contract: POST /api/connections/mint answers as soon as the mint is scheduled — the URL does not exist yet and the card polls for it. A window.open awaiting that URL fires outside the click's user-activation window and is blocked as a popup. That holds on the warm path too: an adopted premint still surfaces on the next poll, not inside the click.

So the click opens a blank tab synchronously and a ref holds it until the URL lands — the one shape that serves both an adopted premint and a cold spawn.

The tab belongs to the moment a mint starts, not to one button: Authorize and Reconnect run the identical mint-and-poll path through onReconnect, so all three go through the same entry point. Its lifetime is one invariant rather than a branch per dead end — while a tab is held, a URL can still arrive only during the in-flight click and the waiting state, so every other resting place (Cancel, a mint reporting failed or expired, a rejected mint, an unmount) reclaims it, and reclaiming resets the click's outcome so it cannot outlive its attempt.

Four further points are deliberate:

  • noopener is not passed to window.open, because that option makes the call return null and the handle is the feature here. The reverse-tabnabbing reference it would have removed is severed by assigning opener = null while the tab is still the same-origin blank document the call just created.
  • The granted tab gets a one-line holding page rather than a bare about:blank, because the mint poll leaves it on screen for seconds on the page's core action. It is written as textContent from an existing catalog key — so no translated string can become nodes, and no new key or locale pass is needed — and it sets color-scheme: light dark with no colours of its own, so it cannot clash with the active theme.
  • The waiting heading is driven by the click's own outcome, not by oauth.minted. minted stays false for the whole poll window, so gating on it told a user whose popup was blocked to finish in a browser page they never got — the claim this PR exists to remove. A flow the card did not start keeps the original rule.
  • A refused tab is not an error. The fallback link is unchanged and is now reached only when the popup was blocked or delivery failed — which is the case its "Re-open" wording actually describes.

Tests

Red-first where red-first is meaningful, and this PR says which tests discriminate rather than implying all of them do.

Pins (fail without the production change, verified by stashing it):

  • the description reserves a two-line floor and clamps at three (jsdom runs no layout engine, so the reserved box is the observable that equal-height rows reduce to)
  • a tab is opened by the click, before any URL exists
  • the approval URL reaches the tab the click opened
  • Reconnect opens a tab too, not just Connect
  • a failed attempt reclaims the blank tab
  • Cancel mid-mint reclaims the blank tab
  • the holding page is written as themeless text, with no markup
  • the heading never claims an open browser page while the tab stands refused — checked inside the poll window, which is where the first predicate was wrong

Guards (pass in both directions by construction, because the state they cover does not exist before this change): a blocked popup still leaves the link as the way in; a tab the user closed is dropped rather than reopened.

One path is deliberately unpinned and the test file says so. A granted tab whose attempt has ended is only observable through a second waiting entry with no click behind it, since a second click sets the outcome explicitly and masks it. Driving that needs a seeded gateway-published banner arriving after a completed click attempt: once a URL is published the card holds the waiting state through expired, and a fresh mount resets the state under test. The fix is a two-line reordering, reviewed against the trace that found it; a contorted fixture would have bought a green tick and no confidence.

One pre-existing test asserted the old heading copy. jsdom grants no window, so its click lands on the refused-tab path where the neutral wording is now correct; it asserts the absence of the browser-page claim instead, because the neutral string is shared with the state badge and a positive match cannot tell the two apart.

Full frontend suite green. tsc -b and eslint clean.

Manual verification

The card-height change was applied to a live pod serving this page and eyeballed against the ragged before-state that prompted the fix.

The tab behaviour needs a real browser — jsdom has no popup blocker and no window manager, so the unit tests pin the call, the delivery and the reclaim, not the browser's own decision. Still to do in a browser: confirm the tab opens on the click and is filled when the URL arrives, and confirm a browser configured to block popups falls back to the link with the neutral heading.

Screenshots / video

Pending — the layout change is a real visual delta and will be attached before this is considered ready, rather than waived. The before-state (ragged rows) is what motivated the change.

Related Issues

no linked issue: both findings came out of a manual re-test of the gallery, not a tracked issue.

This branch is rebased onto main including #7568 (warm handoff), which merged while review round 2 was in flight, so the diff sits on top of that engine rather than beside it.

Note on CI: Backend Tests (Windows) (3) was inherited rather than from this diff — this PR contains zero Python files, and test_security_posture.py::TestGateSideLogRedactorSpelling failed identically at the earlier base commit, where slack/gateway.py carried 7 gate-side log sites against a census of 6.

Pattern harvest

Rule candidate: when a card grid must render flush rows, reserve a floor for the variable-length content rather than pinning it — stretching couples a row's height to its tallest member and breaks when one card expands, while a fixed height silently clips. A floor gets flush rows in the common case and lets the exception grow.

Rule candidate: never let a clamp decide whether a permission or scope disclosure is visible. If a truncatable string ends in what the user is actually granting, either the box must be tall enough for every locale or the disclosure does not belong in that string — and "hover shows the full text" is not a recovery path for touch or keyboard users.

Rule candidate: before adding a window.open for a URL that arrives asynchronously, read the endpoint's own contract for when the URL exists. An endpoint documented as "returns once scheduled; poll for the result" cannot be awaited inside a user gesture, so the tab must be opened by the click and filled later. A code comment that explains why a capability is missing is a defect record, not a design decision.

Rule candidate: attach a fix to the EVENT, not to the button that happens to raise it. Three controls started the same mint here; wiring the tab to one left the other two carrying the original defect, and the predicate meant to describe "a flow this card did not start" quietly described two of its own buttons.

Rule candidate: a resource acquired in a user gesture needs its release expressed as an invariant over the states in which it can still be consumed, not as a branch per failure — and the reset of any flag describing the attempt must sit OUTSIDE the guard that tests whether the resource is still held, or a successful path leaves the flag set forever.

Screenshot evidence

After — the connections gallery at this PR's head, rendered from the built bundle with fixture API state (six providers, first-run "Not connected"). Both rows flush: every description sits in the same reserved two-line floor (GitLab's two-line copy vs the one-liners), and GitLab shows the lettered fallback tile this PR makes reachable.

Connections gallery with flush card rows

Evidence committed at temp-screenshots/connections-flush-rows/connections-gallery-after.png (sha256 ef3febdc61a3193dca91e7967418cce27a140388b7ae7fdc264535d63b2fc286). Captured via network-intercepted Playwright against the built dist — real render, fixture /api/connections/* responses — because a live pod's Disconnect/status reads would touch real grants.

@pepmach
pepmach requested a review from a team September 1, 2026 22:44
@pepmach
pepmach requested a review from a team as a code owner September 1, 2026 22:44
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Design-level review of 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: CONCERNS

Sound shape — sync blank tab plus one reclaim invariant — but delivery is keyed to URL identity, not the attempt, and the browser-dependent core is self-declared unverified.

Watch

  • The delivery effect fires on [approvalUrl] alone, on the stated premise that the URL "arrives once per attempt." A second attempt that surfaces a string-identical URL (an adopted premint, or a stale mcp_oauth banner on a not-verified card whose server.status === 'ok' branch returns before the oauthUrl check) sets a fresh tab ref but never re-fires the effect — the tab is stranded on the "Connecting…" holding page while the heading claims a usable browser page. Key delivery to the attempt (e.g. include the click outcome in the deps or an attempt token), not the URL string.
  • The PR's own description defers the core behavior to a manual pass ("Still to do in a browser: confirm the tab opens on the click and is filled"): the fix's entire premise is popup-blocker/user-activation semantics that jsdom cannot exercise, so a human should see that pass done (including a blocked-popup browser) before merge.

[DESIGN-REVIEWED] 5d0a0a6

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

First-Principles-Verdict: CONCERNS

Both declared fixes earn their place, but over half the diff is other work — three slices verifiably already on main, plus two undeclared riders.

What this change ships

Intent: make the Connections gallery's card rows sit flush and make a Connect click actually open the provider's approval page — a FIX.

  1. Card descriptions reserve two lines, clamp at three, so rows sit flush — justified
  2. Connect/Authorize/Reconnect open an approval tab at click time, filled when the URL lands — justified (browser user-activation rule, a platform constraint)
  3. The blank tab shows a translated "connecting…" holding line during the poll — justified
  4. Heading claims an open browser page only when a tab was actually granted — justified
  5. Blank tab reclaimed on Cancel/failure/unmount; a user-closed tab never reopened — justified
  6. Providers without art (GitLab) get a lettered tile, not an empty gap — rides along, undeclared
  7. Extras install advice overhaul (kiro_crew.extras, docs, 13 locales, 4 screenshots) — rides along; already on main (fix(extras): stop advising an install command that cannot resolve #7533)
  8. Virtualizer holds reading position across an equal-count row swap — rides along; already on main (fix(virtualizer): hold the reader across an equal-count row swap #7811)
  9. Eslint ceiling 599→597 and a dead directive dropped — rides along; already on main (fix(ci): drop a dead eslint directive and re-equal the warning ceiling #7753)
  10. Malformed session/agent ids answer 400, not 500, on three /api/sessions routes — rides along, undeclared

Watch

  • The patch is stale against its base: items 7–9 match main commits 73d959df8, 4593b7df9, 35fcdbc4c byte-for-byte in the merged tree (ci.yml already reads --max-warnings 597; extras.py and the trigger-6 hunk are present), and the description breaks off at "This branch is" — presumably naming the stack. A human should confirm the net merge diff is only items 1–6 and 10.
  • Item 10 (_invalid_session_path_id in dashboard/handlers/core.py, session_workspace.is_valid_id, one test class) has a named harm (unhandled ValueError → 500) and cites the cron.py precedent, but no visible slice owns it: it is unrelated to the connections job and unmentioned by the description. If it is not in an already-merged slice, it needs its own declaration.
  • Item 6 is a genuine fix (the ?? fallback at ConnectionsPage.tsx:622 was unreachable against an always-truthy element), but the description's "What changed" never names it.

Subtractions

[FIRST-PRINCIPLES-REVIEWED] 5d0a0a6

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 5d0a0a6

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

UX-level review of 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

UX-Verdict: PASS

The connect click now opens a real, labeled tab and the heading tells the truth per outcome — the gallery renders flush with an honest recovery path.

Suggestions

  • The waiting heading's clickTab === 'none' && !oauth?.minted branch re-claims "Finish approving in your browser…" for a card that remounted mid-poll (search filter/tab switch), right after the unmount cleanup closed the held blank tab — the exact claim class this PR removes. Since an agent-started flow always carries a URL, gate that branch on approvalUrl being present and fall back to the neutral waiting_for_approval when it isn't; seconds-wide and self-correcting once minted flips, hence suggestion not blocker.

[UX-REVIEWED] 5d0a0a6

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 5d0a0a6

Verdict parsed from the review's SHA-scoped output markers for commit 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 5d0a0a6f8b6c55ea7926a87c9f7cfe50cb6990d7: <one-sentence reason>

@pepmach
pepmach force-pushed the fix/connections-card-and-popup branch from 2a7423a to 4fe1ed3 Compare September 1, 2026 23:11
@pepmach

pepmach commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Disposition — Design 🟡 and UX 🟡 on 2a7423abb, all four items accepted and fixed in 4fe1ed3db

Both lanes converged on the same hole from different angles, and both were right. Nothing here was rebutted.

1. Tab reclaimed on only one of its dead-end paths (Design "Watch"; UX "Suggestions"). Correct, and the diagnosis of the follow-on harm was the part I had not reasoned about: the stale ref meant a subsequent Connect click overwrote it and orphaned the earlier tab permanently, so this was a leak rather than only litter.

Fixed by replacing the single onConnect() === false branch with one invariant: while a tab is held, a URL can still arrive only during the in-flight click and the waiting-for-approval state, so any other resting place reclaims. That covers Cancel, a mint reporting failed/expired, and a rejected mint through one mechanism instead of a patch per branch — the failed-mint branch is now gone, since the invariant already recognises it.

2. Unmount leaves an unfillable tab (Design "Watch"). I had declared this out of scope on the grounds that closing a window out from under the user is worse. That reasoning does not survive the ref's own semantics: the ref is non-null only while the tab is still blank, because delivery clears it. So an unmount cleanup can never close a consent page someone is working in — it can only close a tab nothing will ever fill. Added.

3. Blank interstitial for the whole poll window (UX "Watch", Design "Suggestions"). Accepted. The tab now gets a one-line holding page. Two constraints shaped the implementation: it is written as textContent, never markup, so a translated string cannot become nodes; and it uses the existing connecting key, so this fix needs no new catalog entry and no 14-locale pass. It sets color-scheme: light dark and no colours of its own — a hardcoded dark background would clash with a light theme, which is the trap in "dark-safe background".

4. Blocked-popup users still get the misleading heading, transiently (UX "Watch"). This one was a real defect in the fix itself, not a rough edge: oauth.minted stays false for the entire poll window, so minted && !approvalTabOpened evaluated false and served "Finish approving in your browser…" to a user whose tab the browser had just refused — the exact false claim the PR set out to remove. The boolean could not express it, so it is now a tri-state (none / open / refused) driven by the click's own outcome, with a flow the card did not start keeping the original minted rule.

Pinned red-first: the new heading test fails against 2a7423abb in the poll window specifically, and so do the cancel-reclaim and holding-page tests. Six red-first pins now, up from three. One pre-existing test asserted the old heading copy; jsdom grants no window, so its click lands on the refused-tab path where the neutral wording is correct — it now asserts the absence of the browser-page claim, because the neutral string is shared with the state badge and a positive match cannot tell the two apart.

Separately, for the record: Backend Tests (Windows) (3) is inherited, not from this diff. This PR contains zero Python files, and test_security_posture.py::TestGateSideLogRedactorSpelling fails identically at the base commit ff8dbb252slack/gateway.py carries 7 gate-side log sites where _BASELINE_LOG_SITE_CENSUS says 6. It needs a census bump or a converted call site on main, in its own change.

@pepmach
pepmach force-pushed the fix/connections-card-and-popup branch from 4fe1ed3 to fae2119 Compare September 1, 2026 23:44
@pepmach

pepmach commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Disposition — round 2 on 4fe1ed3db, three findings accepted and fixed in fae211905, one deferred by the reviewer's own terms

Nothing rebutted. Also: this branch is now rebased onto main including #7568, which merged while this round was in flight — so the diff you see sits on top of the warm-handoff engine rather than beside it.

1. Point patch — Authorize and Reconnect bypassed the fix (First Principles and Design, same finding). Accepted without qualification, and the framing was fair: my comment claimed clickTab === 'none' meant "a flow this card did not start", which the diff contradicted — :731 and :769 are this card's own buttons calling the identical connectionsMint-and-poll path through onReconnect. Two of three mint-starting buttons opened no tab and still served the false heading.

Fixed by moving the entry point from the button to the event: startConnect became startMint(begin), taking the action that starts a mint. Connect passes onConnect, Authorize and Reconnect pass onReconnect. Pinned red-first — the new Reconnect test fails against 4fe1ed3db.

2. Stale 'open' re-lies on replay (UX). Accepted; this was a real bug and the trace was exact. Delivery nulls the ref, so a reclaim effect guarded on the ref first skipped the reset after a successful connect and left clickTab at 'open' indefinitely. The reset now sits above the ref guard, so a click's outcome cannot outlive its attempt.

This one is deliberately unpinned, and the test file says so rather than faking it. The stale outcome is only observable when the card re-enters waiting-for-approval with no click behind it — a second click sets the outcome explicitly and masks it. I tried to drive that: once a URL is published the card holds the waiting state through expired (8s, past the mint poll), and a fresh mount resets the state under test. Producing it would need a seeded gateway-published banner arriving after a completed click attempt. Rather than contort a fixture into a green tick, the gap is recorded in the file next to the code it covers.

3. Two-line clamp can hide the write-scope warning (UX). Accepted, and I want to be clear that this was a regression this PR introduced: before the clamp the text wrapped freely and nothing was hidden, so it is mine to answer rather than a pre-existing issue.

I measured rather than guessed. Longest translation per value prop, excluding the pseudo-locale:

provider English longest locale
gitlab 83 97 it
notion 58 78 ru
linear 58 66 de
atlassian 45 56 fr
stripe 49 53 de
vercel 45 51 es

GitLab's is the one that carries "this grant can also write", and it is also the longest and grows the most in absolute terms. I cannot prove how 97 characters wrap at the three-column width — jsdom has no layout engine — so I did not want a green test standing in for that.

The box is therefore a two-line floor with a three-line ceiling (min-h-[34px] line-clamp-3) instead of a fixed two-line height. Rows stay flush for every English value prop, since two lines is what the longest of them needs; a locale that needs a third line grows that one card rather than hiding a permission scope behind a hover-only title that touch and keyboard users cannot reach. Growing one card by 17px is the cheaper failure. The agents gallery card pins an exact height because no permission scope rides in its text — that difference is now stated in the code.

The durable fix is that a scope disclosure should not be the truncatable tail of a marketing sentence at all. That needs a registry field and a catalog pass, so it does not belong in this PR.

4. Refused-state copy duplicates the status chip (UX, Suggestions). Deferred on the reviewer's own terms — it is scoped to "when the connections-copy slice lands", and a new "Open the approval page" string is a 14-locale change this PR deliberately avoids. Noted, not actioned.

Structure: the two commits became one. Commit 1's subject and body described "a pinned two-line box", which finding 3 made false, and a message that misdescribes its own diff is worse than a squash. One commit now, within the hygiene ceiling.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@pepmach
pepmach force-pushed the fix/connections-card-and-popup branch from fae2119 to c67efb4 Compare September 2, 2026 05:19
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
…ery mark

Three findings from a manual re-test of the Connections gallery.

ONE: cards in a row were not flush. Provider value props differ in length --
GitLab's wraps to two lines where Notion's takes one -- and a description with
no reserved height makes each card as tall as its own copy, so the row renders
ragged.

The description now reserves a two-line FLOOR with a three-line ceiling. Two
lines is what every English value prop needs at most (the longest, GitLab's, is
83 characters and takes exactly two), so reserving them makes the row flush. The
ceiling is three rather than a fixed two because pinning it would CLIP, and the
clipped tail is the consequential part: GitLab's copy ends in "this grant can
also write" and translations run materially longer (Italian 97 characters,
Russian Notion 78 against English 58), so at the three-column width a verbose
locale could hide a write-scope disclosure behind a hover-only `title` that
touch and keyboard users cannot reach. Growing one card by a line is the cheaper
failure. The agents gallery card pins an exact height because no permission
scope rides in its text.

The grid keeps `items-start`: a card expanded into the approval flow must not
drag its row partner up to its height, which is why this is fixed on the content
rather than by stretching the cards.

TWO: no mint-starting click opened the provider's approval page. The minted URL
only ever surfaced as a link labelled "Re-open approval" -- wording for a window
that had never been opened -- so the first step of connecting was a link the user
had to notice, and the card's own comment recorded the gap rather than closing
it.

The ordering is what makes this more than a window.open() call. POST
/api/connections/mint answers as soon as the mint is SCHEDULED, before the URL
exists, and the card polls for it; a window.open() awaiting that URL fires
outside the click's user-activation window and is blocked as a popup. That holds
on the warm path too, where an adopted premint still arrives on the next poll
rather than inside the click. So the click opens a blank tab synchronously and a
ref holds it until the URL lands -- the one shape that serves both an adopted
premint and a cold spawn.

The tab belongs to the moment a mint STARTS, not to one button: Authorize and
Reconnect run the identical mint-and-poll path, so all three go through the same
entry point. Its lifetime is one invariant rather than a branch per dead end --
while a tab is held, a URL can still arrive only during the in-flight click and
the waiting state, so every other resting place (Cancel, a mint reporting failed
or expired, a rejected mint, an unmount) reclaims it.

Four further points are deliberate:

- `noopener` is not passed to window.open, because it makes the call return null
  and the handle is the feature; the reverse-tabnabbing reference is severed by
  assigning `opener = null` while the tab is still the same-origin blank
  document.
- The granted tab gets a one-line holding page instead of a bare about:blank,
  since the mint poll leaves it on screen for seconds on the page's core action.
  It is written as text from an existing catalog key -- no new key, no locale
  pass, and no translated string can become nodes -- and sets `color-scheme`
  rather than colours, so it cannot clash with the active theme.
- The waiting heading is driven by the click's own outcome, not by
  `oauth.minted`. `minted` stays false for the whole poll window, so gating on it
  told a user whose popup was blocked to finish in a browser page they never
  got, which is the claim this change exists to remove. A flow the card did not
  start keeps the original rule, and the outcome is reset when its attempt ends
  so a later mint cannot inherit it.
- A refused tab is not an error. The fallback link is unchanged and is now
  reached only when the popup was blocked or delivery failed, which is the case
  its "Re-open" wording describes.

THREE: a provider that ships no brand mark rendered an empty gap where its mark
belongs -- GitLab today, since the art inventory carries GitHub, which the launch
set holds back, and not GitLab, which it includes.

The card intended a lettered tile for that case and could never reach it: it
built `<ProviderLogo …/>` unconditionally, and a JSX element is truthy even for a
slug with no mark, so the `??` beside it never evaluated. The component returns
null when RENDERED, which `??` cannot observe. The roster is now asked of the
module instead, which makes the fallback reachable and the ProviderLogo
docstring's claim about it true. The mark itself is not added here: brand art is
a deliberate choice, and the tile is the honest interim.

Tests, red-first where red-first is meaningful: the description reserves a floor
and clamps at three; a tab is opened by the click before any URL exists; the URL
reaches that tab; Reconnect opens one too; a failed attempt and a cancelled one
each reclaim the blank tab; the holding page is written as themeless text; the
heading never claims an open browser page while the tab stands refused, checked
inside the poll window where the first predicate was wrong; and a provider with
no mark renders its initial while one with a mark renders no letter. Two guards
pass in both directions by construction, since a blocked-popup path and a
user-closed tab do not exist before this change.

One path is deliberately left unpinned and says so in the test file: a granted
tab whose attempt ended is only observable through a second waiting entry with
no click behind it, which this harness cannot produce in one mount.

One pre-existing test asserted the old heading copy. jsdom grants no window, so
its click lands on the refused-tab path where the neutral wording is correct; it
asserts the absence of the browser-page claim instead, because the neutral
string is shared with the state badge and a positive match cannot tell them
apart.
@pepmach
pepmach force-pushed the fix/connections-card-and-popup branch from c67efb4 to b0e40a3 Compare September 2, 2026 06:00
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Sep 2, 2026
@github-actions github-actions Bot added the readiness: passed Eligible automated validation passed for the current revision label Sep 2, 2026
@iamwhatever
iamwhatever merged commit ebd5905 into main Sep 3, 2026
75 of 78 checks passed
@iamwhatever
iamwhatever deleted the fix/connections-card-and-popup branch September 3, 2026 01:24
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 3, 2026
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