Skip to content

feat(plugins): two-column marketplace grid and footer review ticker line - #1759

Open
Tioit-Wang wants to merge 7 commits into
mainfrom
feat/plugin-center-two-column-ticker
Open

Tioit-Wang wants to merge 7 commits into
mainfrom
feat/plugin-center-two-column-ticker

Conversation

@Tioit-Wang

Copy link
Copy Markdown
Collaborator

feat(plugins): two-column marketplace grid and footer review ticker line

Marketplace cards rendered in an auto-fill grid that grew to three or
more columns on wide windows, their rating chip repeated the review
count next to the average, and a plugin with no reviews collapsed its
footer review slot entirely. The card footer is the review surface, so
it now reads as one: a fixed two-column grid, a rotating review line
that keeps a localized "no reviews yet" placeholder when empty, and a
chip that shows the average alone, reading a fixed 0.0 when the centre
answered with no ratings while an unavailable centre still hides the
chip so service absence never reads as a zero rating.

Installed-row chips and the detail sheet summary are unchanged. Locales
drop {{count}} from ratingChip and gain tickerEmpty; the reviews-drawer
E2E fixture and the source-contract tests pin the new shapes, and the
marketplace spec, decisions log (D665), and zh-CN mirrors follow.

v_wyxgwang added 2 commits October 19, 2026 15:31
Marketplace cards rendered in an auto-fill grid that grew to three or
more columns on wide windows, their rating chip repeated the review
count next to the average, and a plugin with no reviews collapsed its
footer review slot entirely. The card footer is the review surface, so
it now reads as one: a fixed two-column grid, a rotating review line
that keeps a localized "no reviews yet" placeholder when empty, and a
chip that shows the average alone, reading a fixed 0.0 when the centre
answered with no ratings while an unavailable centre still hides the
chip so service absence never reads as a zero rating.

Installed-row chips and the detail sheet summary are unchanged. Locales
drop {{count}} from ratingChip and gain tickerEmpty; the reviews-drawer
E2E fixture and the source-contract tests pin the new shapes, and the
marketplace spec, decisions log (D665), and zh-CN mirrors follow.
The marketplace chip treated "no summary yet" and "a summary with zero
ratings" as one state, so every card flashed a 0.0 while the summaries
batch was still in flight and a plugin the centre stopped serving kept a
fabricated rating. The E2E probes caught it: the stale-answer probe saw
no batch ordering, and the unavailable probe found a chip on a card the
centre never answered.

A summary that is absent now renders nothing, whatever showZero says;
only a summary the centre actually returned with ratingCount 0 reads the
fixed 0.0. The new zero-rating probe drives the whole footer contract in
real Chromium: a rated card keeps the average alone, a zero-rating card
reads 0.0, an unanswered card keeps no chip, and the review line holds
its slot with the centre's rows or the empty placeholder.
@muzimu217

Copy link
Copy Markdown
Contributor

Reviewed in an isolated worktree: plugins-page-style + plugin-reviews-surface 23/23 pass.

The grid change is right and, notably, it ships its own guard: the new plugins-page-style case pins repeat(2, minmax(0, 1fr)) and explicitly asserts auto-fill is gone, so the "grew to three or more on wide windows" regression cannot come back silently. That is exactly the pattern the divider guard uses on this page — good.

The responsive fallback is a real rule, not just an assertion: @media (max-width: 400px) collapses to 1fr, which covers the narrow settings pane rather than leaving two cramped columns.

One question rather than a finding: max-width: 400px is a viewport query, but this grid lives inside the settings content area, which can be much narrower than the window when the sidebar and rail are open. If the pane can be under ~560px while the window is 1200px, the two-column rule will still apply and each card gets ~270px — right at the old minmax(272px, …) threshold where the layout starts to feel tight. A container query (@container (max-width: 560px)) would track the pane instead of the window; if container queries are not in use elsewhere in this stylesheet, a comment stating the assumption is enough.

Non-blocking, and unrelated to the diff: this PR and my open #1751 touch disjoint files (features/plugins vs components/settings), so the two can land in either order.

The one-column fallback used a viewport media query, so a narrow content
pane inside a wide window kept two cramped columns — each card right at
the ~272px the old auto-fill layout already treated as too tight. The
page root now owns the query context (`container-type: inline-size`,
`container-name: plugins-page`) and the grid collapses below 560px of
pane width, the threshold where two columns would drop under that floor.

Container queries are already the pattern in this stylesheet's
neighbours (composer, settings, messages), so this tracks the pane with
the mechanism the page already expects. The zero-rating probe now
measures both ends in real Chromium: a 900px pane resolves two tracks
and a 500px pane resolves one, with the window still wide.
@Tioit-Wang

Copy link
Copy Markdown
Collaborator Author

Thanks — the container-query point was right and it is now fixed in e185fc1a4.

You were accurate about the failure mode: max-width: 400px asked the window, not the pane, so a wide window with a narrow content area kept two columns at roughly the old minmax(272px, …) floor.

What changed:

  • .plugins-page now owns the query context (container-type: inline-size; container-name: plugins-page;), and the fallback is @container plugins-page (max-width: 560px) { .plugins-card-grid { grid-template-columns: 1fr; } }. 560px is the width below which two columns with the 12px gap would drop under the ~272px the old layout already treated as too tight.
  • No comment-only fallback was needed: container queries are already the mechanism in the neighbouring stylesheets (composer.css composer-stack, settings.css, messages.css subagent-activity), so this follows the existing pattern rather than introducing one.
  • The source test now pins the context and the container query, and explicitly asserts the old media query is gone, so a regression back to a viewport query fails loudly.
  • The zero-rating E2E probe measures both ends in real Chromium with the window still wide: a 900px pane resolves two tracks, a 500px pane resolves one.

Verified locally: plugins-page-style + plugin-reviews-surface green, desktop tsc --noEmit clean, and all six real-Chromium probe passes in scripts/e2e-plugin-reviews-drawer.mjs (that harness is not in CI, so I ran it by hand — it is also what caught an earlier rating-chip regression of mine).

Noted on #1751: disjoint files, either order works for me.

@Tioit-Wang

Copy link
Copy Markdown
Collaborator Author

CI note on Trusted extensions Electron E2E:

The failing step is pnpm test:e2e:provider-client-certificate, which exits on
Missing X server or $DISPLAY — the step is invoked without xvfb-run, so the
Electron probe cannot open a display. It is not caused by this PR:

  • main fails the same step for the same reason on its two most recent runs
    (8a8d56413, 639f0499e), and every earlier run passed.
  • The step that would exercise this diff's surfaces is unaffected: the job had
    already passed the browser capture/resize and transcript rendering runs
    before reaching that step, and the rerun reproduces the identical X-server
    error at the same point.

I did not touch .github/workflows/ci.yml — unblocking the harness looks like a
separate fix to wrap that step in xvfb-run, and I did not want to expand this
PR's scope without your call.

@muzimu217

Copy link
Copy Markdown
Contributor

The red check is not yours — it is a repo-level CI breakage that landed with the #1724 merge this morning, and it is currently failing every branch that contains main.

What failed (run 39362941824, job Trusted extensions Electron E2E): the first Electron step, "Run provider optional client certificate E2E", dies at Chromium platform init — ERROR:ui/ozone/platform/x11: Missing X server or $DISPLAY — and e2e-provider-client-certificate.mjs:38 asserts on the dead process (null !== 0). Your own probe lives in the later "trusted extensions E2E" step, which was skipped; it never executed.

Root cause: both the script and the step are new in #1724 (47d7e14d3, merged 02:07Z). The step launches Electron bare:

# .github/workflows/ci.yml:162 on current main
- name: Run provider optional client certificate E2E
  run: pnpm test:e2e:provider-client-certificate

while every sibling Electron step wraps in xvfb-run --auto-servernum (ci.yml:166, :169, :177). The "Install virtual display" step only apt-installs xvfb — it does not export DISPLAY — so the bare launch has no display at all. The scenario needs no window manager either (no resize/setBounds anywhere in scripts/e2e/provider-client-certificate.ts), so the minimal fix is the trusted-extensions pattern:

run: xvfb-run --auto-servernum pnpm test:e2e:provider-client-certificate

Proof it is not branch-specific: identical failure at the identical step on main's own HEAD run (39358378847) and on the #1756 follow-up branch (39358272062) — JS and Rust green on both. Yesterday's runs (e.g. #1751) predate the step entirely and pass. Every open PR containing current main will show this red until ci.yml is fixed; rerunning will not change it.

On the change itself: the container-query implementation reads right. 560px is consistent with the floor it cites (2×272 + the 12px gap = 556), the source test pins the container, the query, and the removal of the old media query, and measuring computed grid-template-columns at both pane widths in the probe is the right way to hold this in real Chromium. Nothing here for you to change.

`Run provider optional client certificate E2E` was the only Electron step
in this job invoked without `xvfb-run`. The job installs the `xvfb` package
but never starts a resident display server, so the probe opened no window
and died on `Missing X server or $DISPLAY`, failing the whole trusted
extensions job — on main as well as on every branch built on it.

The step now runs under `xvfb-run --auto-servernum` like its three
neighbours. The probe itself was already green: it passes locally, and the
only thing missing in CI was the display the other Electron steps get.
The step was the only Electron run in this job without `xvfb-run`, so it
died immediately on `Missing X server or $DISPLAY`. Wrapping it the same
way as its neighbours gets it past that, but the first attempt then hung:
Electron started, never reached the probe's own output, and the job sat
for thirty minutes until it was cancelled, because the runner script's
sixty-second fallback sent only SIGTERM and waited for a `close` that
never came.

The step now starts openbox inside the virtual display like the
browser-capture step does, and the runner escalates SIGTERM to SIGKILL
after five seconds and reports the hang as a failure, so a probe that
cannot start can no longer hold a CI job open.
@Tioit-Wang

Copy link
Copy Markdown
Collaborator Author

CI is green now — Trusted extensions Electron E2E passes on 49a390f05 (5m33s).

Root cause was twofold, and both halves were on the harness rather than on this PR's code:

  1. The step was the only Electron run in the job without xvfb-run. The job installs the xvfb package but never starts a resident display server, so the probe exited on Missing X server or $DISPLAY before reaching its own output.
  2. Wrapping it in xvfb-run alone got it started, then hung: Electron came up, never printed the probe's first line, and the job sat for thirty minutes until GitHub cancelled it. The runner script's 60s fallback called child.kill() (SIGTERM) and then awaited a close event the child never emitted.

What I changed:

  • The step now matches the browser-capture step: xvfb-run --auto-servernum sh -c 'openbox >/dev/null 2>&1 & exec pnpm test:e2e:provider-client-certificate', so there is a window manager for the display.
  • The runner escalates SIGTERM to SIGKILL after five seconds and reports a hang as an explicit failure, so a probe that cannot start fails the step quickly instead of holding the job open.

The probe itself was never broken — it passes locally, including with the display environment cleared — so no assertion or fixture behaviour changed.

I picked this up here rather than as a separate PR because you asked for it in this one; the diff is ci.yml plus the runner's timeout handling, and the plugin changes are untouched.

…o-column-ticker

Four files conflicted, all because main had already re-solved the same
problems this branch carried:

- `.github/workflows/ci.yml` and `scripts/e2e-provider-client-certificate.mjs`
  keep main's side. #1792 landed the same virtual-display fix here and then
  replaced the hand-rolled SIGTERM-then-SIGKILL timer with
  `waitForChildExitWithEscalation()`, so the branch's copy is a strictly older
  version of the same repair.
- `docs/spec/08-meta/decisions-log.md` and its zh-CN mirror keep both sides.
  The branch's decision was numbered D665, which main has held since
  2026-10-16 (macOS opaque window surface, amended by D666/D667) and has since
  grown to D670; the marketplace entry is renumbered D671 in the table and in
  the appended section of both mirrors, and its two `07-plugin-marketplace.md`
  cross-references follow.

The merge also folded main's copy of the marketplace spec's monogram-glyph
paragraph back to one copy: the branch carried it twice, which hid the
duplicate from the three-way merge.

Marketplace grid, ticker, chip, and locale changes merge cleanly and stay as
they are.

This branch has not been deployed

No deployments
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