Add SpecResults: run status pills + progress bar (React) - #719
Open
ryanjwilke wants to merge 50 commits into
Open
Add SpecResults: run status pills + progress bar (React)#719ryanjwilke wants to merge 50 commits into
ryanjwilke wants to merge 50 commits into
Conversation
A run's per-status spec breakdown (failed/errored/passed/skipped/ remaining) as filterable pills plus a proportional progress bar, replacing the old NoticeSpecsQueuedRunning well pattern. Ported from a live design-iteration prototype in cypress-services (frontend/packages/dashboard/src/specs/SpecsStatusStripPrototype.tsx, PR #14230), plus one new capability that prototype didn't have: a "scheduled to complete" state for a run held open by the project's completion delay. React + constants only for now -- Vue is a planned fast-follow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ainer query The instructions.md language was copied from the reference web-component implementation (which used container queries via Shadow DOM's container-type), but the actual React component uses Tailwind's sm: viewport breakpoint. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The reference material this was ported from already framed it this way
("Patterns · Cypress Cloud"), which got lost when it was first filed
under the flat Components list. Unlike Card/Button Bar/Test Results
List, SpecResults keeps a real installable package underneath (its
state-derivation logic isn't a copy-paste recipe) -- getAllComponents()
now excludes it from the auto-generated Components index via a small
PATTERN_ONLY allowlist, while the package itself still lives under
components/SpecResults/ where the build/publish tooling expects it.
Also adds a live docs/src/demos/SpecResults.astro demo (client:only=
"react") so the pattern page actually renders the component instead of
just showing code -- this component had no live preview at all before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The flat Usage+code-block template shared by Card/Button Bar/Test Results List undersold this one -- the reference material's own intent was closer to a foundational page like /colors: an eyebrow, a big headline with an italic accent, a lede, and stat badges, ahead of the actual sections. Reuses the exact Tailwind classes /colors/index.astro already establishes for that hero treatment (font-brand-heading, the eyebrow/headline/lede sizing) rather than introducing new bespoke CSS, so this stays visually consistent with the one other page that already uses this pattern in the real site. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous pass ("inspired by /colors") was too thin -- a bare hero
plus flat Usage+code-block sections. This replaces it with a full
custom Astro page (docs/src/pages/patterns/spec-results.astro,
converted from .mdx since MDX can't do the arbitrary layout control
this needs) that actually reproduces the reference material's
structure: Basic usage, a 9-state Component states stack with
captioned boxes, an API props table, an Interaction table, When to
use it, and Guidelines -- with a right-side "On this page" TOC via
BaseLayout's tocHeadings, matching the reference page's own outline
exactly.
Two real bugs found and fixed along the way, not just layout:
- Astro's client:only islands serialize props to JSON before
hydrating, so a function passed as onCancel from the .astro
frontmatter silently became null and every Cancel button in the
demos disappeared. Added SpecResultsCancelable.tsx, a thin wrapper
that defines the callback inside the hydrated island instead of
passing it in from outside -- the standard workaround for this
Astro limitation. Confirmed via the actual serialized
astro-island props attribute before and after.
- The reference eyebrow ("Patterns · Cypress Cloud") used
letter-spacing: 2px that got dropped when the hero was first built
from /colors' plain eyebrow class; restored via tracking-[2px].
Also fixes Sidebar.astro's pattern-page glob (previously .mdx only)
to pick up .astro pattern pages too, and gives the astro:components
Code block real padding/font-size/rounding -- it renders bare by
default with none of that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
h-5 + inline-flex items-center to fix the height instead of letting line-height/py-0.5 set it, and text-purple-500 to match the token color used elsewhere on the page (package-name pill, prop types). Applies to all 15 inline <code> references across the page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
At px-8 padding the row needed ~722px but only had ~706px available -- a ~16px shortfall that tripped flex-wrap and pushed Cancel run onto its own line. Dropping to px-6 reclaims exactly enough width; confirmed via the pills/button now sharing the same row offset. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cypress Cloud (cypress-services' dashboard) still vendors bootstrap-
sass, which sets html { font-size: 10px } globally instead of the
standard 16px -- so every rem-based Tailwind utility renders at 62.5%
of its documented value there (gap-2, nominally 8px, measures 5px).
Confirmed live in that dashboard via getComputedStyle after this fix:
pill height 24px, gap 6px, font-size 16px, strip height 56px -- all
correct now, versus visibly compressed before.
Converts every spacing/sizing class in CssClasses (constants/src/
index.ts) and the Cancel button's gap in react/SpecResults.tsx to
literal [Npx] arbitrary values. rounded/rounded-b/rounded-tl-none/
rounded-tr-none are left as-is -- this design system's own theme
hardcodes borderRadius.DEFAULT/.md to a literal 4px rather than a rem
value, so those were never affected.
Documented as a known issue in architecture.md and instructions.md:
this is a workaround for that one consumer's bug, not a flaw in this
component or in the design system's own Tailwind preset. A consumer
with a normal 16px root renders [24px] and h-6 identically either
way.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NOTESTS and CANCELLED are distinct RunInstanceStatusEnum values, so callers should not have to pre-sum them. skipped/cancelled now combine into one "skipped" pill and one Specs-tab filter link, same as before, but the component does the summing instead of the caller.
- Props table's first column matches Type/Default's text-sm (was inheriting the base 16px instead). - Every table's first/last cell drops its outer-edge padding (left on the first column, right on the last) now that it sits flush against the table's own border. - Props table header cells use the Poppins heading font and heading color instead of the small gray label treatment.
Replace the 4-column Prop/Type/Default/Description table with a per-prop stack: the prop name as a code label, then Description/Type/ (Default when one exists) rows underneath. Reads better for long description text than a table cell ever did.
Matches the referenced Atlassian-style layout: a hairline directly under the prop-name chip, not just between the Description/Type/ Default rows underneath it.
text-base -> text-sm on the prop-name label, and mb-10 -> mb-16 between each prop's stack.
Every run tab (including Specs itself) is already a path segment under runs/:id/, so "../specs" resolves one level too far up and drops the run id -- e.g. from .../runs/456/overview it lands on .../runs/specs instead of .../runs/456/specs. Verified with node's URL resolution. The scheduled-to-complete pill's ../../settings/general was already correct (it genuinely needs to climb out of runs/:id/ to the project root) and is untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 4a1121a The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cypress-design
|
||||||||||||||||||||||||||||
| Project |
cypress-design
|
| Branch Review |
spec-results-component
|
| Run status |
|
| Run duration | 02m 12s |
| Commit |
|
| Committer | Ryan Wilke |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
2
|
| View all changes introduced in this branch ↗︎ | |
Warning
No Report: Something went wrong and we could not generate a report for the Application Quality products.
sm:h-[56px] was a fixed height, so a run with enough pills + Cancel run to wrap onto a third line had that line clipped/overlapping the tick bar instead of the container growing to fit. sm:min-h-[56px] keeps the usual single-line height while letting wrapped content push it taller.
sm:pt-0 sm:pb-[4px] zeroed out the base pt-[12px]/pb-[16px] at desktop width, relying on the old fixed sm:h-[56px] + justify-center to center a single line without needing padding. Now that height is min-h-[56px] (so wrapped content can grow the container), that trick left wrapped content flush against the top/bottom edges with no breathing room. Dropping the sm: overrides restores real padding at every breakpoint; justify-center still centers a single line fine since 12+24+16=52px is under the 56px minimum. Also trims the docs page's "Basic usage" demo box from py-8 to py-6 now that the component itself carries real padding.
…iner query sm: is a viewport media query, so a strip embedded somewhere narrower than the browser window (a sidebar, a split pane) would never stack even though its own rendered width can't fit the row layout. The strip now declares itself a query container (@container) and its children key off @[576px]: -- an arbitrary-value container variant, not the @tailwindcss/container-queries plugin's named @sm:/@XL: scale, since that scale is rem-based and would hit the exact root-font-size bug this component already works around everywhere else (see the "Known issue" note in architecture.md). 576px is the plugin's xl step evaluated at a normal 16px root -- the closest named container breakpoint to the ~600px threshold, kept as a literal px value instead. Both cypress-design's own Tailwind preset and cypress-services' already register @tailwindcss/container-queries (confirmed in cypress-services' tailwind.config.cjs via the shared TailwindConfig() preset), so no consumer-side setup is needed. Adds a regression test mounting the strip in a narrow (300px) fixed-width wrapper at Cypress's default (wide) viewport, asserting it stacks -- the exact case a viewport media query would get wrong.
A raw stroke="#..." attribute on the source SVG has the lowest
possible CSS priority, so every consumer's own auto-generated
icon-light-{color} class (from constants-statusicon's per-status
config) silently overrode it -- the on-page color never actually
changed, confirmed via getComputedStyle after the fact. Only the
stroke-opacity edits were ever real, since there's no class rule
setting that property. Reverting the color hack entirely; the real,
per-surface fix lives in SpecResults itself (next commit), via
className overrides rather than editing a shared icon's own default.
The real fix for the previous commit's problem: instead of touching a
shared icon's own hardcoded default, override className per call site
with the DS's existing icon-{light,dark}-{color} class system --
!icon-light-gray-200 for running on the strip's white background,
!icon-light-gray-700 for running inside a dark tooltip row,
!icon-dark-gray-700 for queued (single icon-dark-only ring) in the
same dark row. Also darkens the Auto Cancellation lightning bolt one
shade (indigo-300 stroke, was indigo-200) so the stroke isn't the
brightest element in the row.
Also:
- Tooltip text bumped to 14px/20px, matching RunResults' own tooltip
convention (the shared Tooltip's bare default, 16px/24px, is sized
for a single short label).
- Scheduled-to-complete tooltip's body copy tightened to match the
exact wording requested, and its CTA shortened from "Update settings"
to "Update setting" (singular -- one setting, not a page section).
Docs: new architecture.md section covering the per-surface override
pattern and -- explicitly -- the raw-attribute mistake it replaces,
including how to verify a color fix actually landed
(getComputedStyle, not getAttribute).
react-spec-results/package.json gained two new dependencies this PR (@cypress-design/react-tooltip, @cypress-design/react-icon) but the root lockfile was never regenerated to match. CI's --immutable install correctly refused to proceed rather than silently modify a lockfile it wasn't told to -- failing Build and Vercel both, since both run the same install step.
@[576px]:min-h-[56px] was on the same element as @container -- a container query can only be consumed by a container's descendants, never by the element that establishes @container itself. Confirmed via getComputedStyle: it computed min-height: 0px at every width, silently never matching (caught by Cursor Bugbot, PR #719). Made it unconditional instead of moving @container to a wrapper: a min-height floor is harmless at every width (it only has any visible effect on the single-line desktop layout, where natural content height can be under 56px -- the stacked/narrow layout's natural height already exceeds it, so the floor is a no-op there). Verified both widths directly: 700px reads min-height 56px (previously 0px); 300px reads min-height 56px with actual content height 206px, i.e. no narrow-width regression.
…ading JQuery Spreading a JQuery<HTMLElement> collection requires downlevelIteration under this tsconfig's target; .toArray() is jQuery's own array conversion and needs no iteration protocol at all.
…icon New SpecResultsWithDescription/SpecResultsArchivable demo wrappers and the IconActionArchive source SVG, covering timed-out/errored/cancelled/no-tests states for the docs site.
…tooltip isComplete lets a caller mark a timed-out/abandoned run as complete even though it still carries a nonzero queued count, so Archive can show where the derived state alone would hide it. The remaining pill's tooltip now always breaks running/queued apart, same as the skipped pill already does -- "N specs remaining" doesn't say which state those specs are actually in, so the reason is worth naming even when only one cause contributes.
- Add isComplete and the missing spec-results-archive selector/Archive run row to instructions.md and the docs Props/Interaction tables; mention Archive in the accessibility section - Fix the remaining-pill tooltip docs to match the always-render behavior - Drop the redundant "mirrors onCancel" aside from onArchive's description now that it's documented as its own thing - Standardize on "cancelled" (not "canceled") across the component's docs and demo copy - Fix the manually-cancelled demo's timestamp: no gap before the period, medium weight, real timestamp as a title tooltip
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 14320c7. Configure here.
…fset isComplete now also requires !scheduledToComplete -- a run held open by the completion delay had nothing running or queued, so it read as complete and showed Archive next to the live countdown pill. Splits the old isComplete check into allSpecsFinished (still gates the scheduled-to-complete pill/tick, unchanged) and isComplete (now excludes the delay window). The first pill's -ml-[6px] offset (canceling its own px-[6px] to align with the description text above) only applied via index===0, so any pill that started a second row -- stacked below 576px, or wrapped above it -- kept its padding and sat 6px right of where it should. Moved the offset onto the pills container itself so every row gets it.
border-b/border-t alone only set border-width; they render nothing without a border-style, which this component was relying on Tailwind preflight's *,::before,::after default (border-style: solid) to supply. cypress-services' own build doesn't carry that preflight declaration, so the description divider and the two button-row dividers rendered with zero visible border despite computing a width and color. The outer strip card's own border already did this correctly (border border-solid); the three newer dividers didn't match it.
…utility The named border-solid utility set border-style: solid unscoped (all four sides), and since it's already used elsewhere in cypress-services' own source, adding it here also affected how that utility renders on unrelated elements already carrying the class -- confirmed by diffing a build with and without this change: an unrelated Button's uniform 1px border became uneven (1.5px on three sides) purely from this component picking up the same class name. Replaced with a directional arbitrary-value selector ([border-bottom-style:solid] / [border-top-style:solid]) instead -- these compile to a selector no other class name can coincide with, so they can only ever affect this component's own three dividers.
cypress-services doesn't supply Tailwind's border-style: solid preflight default, so a bare border-b/border-t renders no visible border there, and the obvious first fix (the named border-solid utility) caused a real regression on an unrelated component. Tracked as PD-42 to fix at the actual source instead of every component needing this workaround.
The circle-slash "skipped" icon was only ever a stand-in (no dedicated cancel icon exists in the design system yet); it read as a status glyph rather than a cancel action, so drop it and let the button's text carry the label. Also documents the dist/-vs-source gotcha this fix ran into while iterating locally, so it's not rediscovered the hard way again.
Cypress Cloud is the only real consumer of this component right now, so depend on react-router-dom directly rather than adding a generic link-injection prop for a hypothetical non-router consumer that doesn't exist yet. Clicking a pill was doing a full browser navigation (reload the document, re-fetch and re-execute the whole bundle) even though the Specs/Test Results tabs it links to already live in the same app shell. Deliberately not react-router-dom's own <Link to="..."> for the navigation itself: v6 resolves a relative `to` against the full current URL (including whatever a wildcard tab route already consumed), so from "/runs/6/specs" it turned "specs?..." into the nonsense "/runs/6/specs/specs?...". A real <a href> doesn't have that problem -- same relative resolution any browser link uses -- so this keeps the authored href as-is, lets the browser's own URL resolution compute the target, then hands that to useNavigate() instead of letting the click fall through to a real navigation. Falls back to an unmodified plain anchor outside a Router (this component's own docs demos render with none) -- useInRouterContext is safe to call unconditionally, but the hooks that do real navigation work throw outside a Router, so the conditional branches on which component gets mounted rather than which hooks get called.
Per review: a pattern is one component shown in its various uses across applications; SpecResults is itself a component. The existing docs/src/pages/components/[component].astro pipeline already handles a React-only package -- it derives the framework list from react/ existing, hides the framework tabs for a single framework, renders react/ReadMe.md + instructions.md, and accepts an optional .astro live demo -- so the hand-built pattern page and its plumbing are unnecessary. - Revert docs/src/lib/components.ts (PATTERN_ONLY) and docs/src/components/Sidebar.astro (patterns *.astro glob) to main. - Delete docs/src/pages/patterns/spec-results.astro and the three .tsx island wrappers that lived under pages/ (Astro warned "Unsupported file type ... found in pages directory" for each on every build). - Add docs/src/demos/SpecResults.astro: a single client:only="react" island backed by SpecResultsDemo.tsx, which renders the same 14 labelled states the pattern page had. Callbacks and description nodes are created inside the island, so no per-callback wrapper files are needed. The manual-cancel example no longer hot-links a third-party avatar service. - .agents/index.md: drop the Patterns section (its Card / Button Bar / Test Results List instructions.md URLs were never published by copy-agents.mjs) and list SpecResults under Components. - architecture.md / instructions.md: the auto-render was never blocked on a Vue demo; say what the component page actually renders today. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What & why
Cypress Cloud's Run Overview page had a design-exploration prototype
(
SpecsStatusStripPrototype.tsx, cypress-services PR #14230) replacing theold
NoticeSpecsQueuedRunning"well" notice — a row of per-status pills(failed/errored/passed/skipped/remaining), each linking to the Specs tab
filtered to that status, plus a proportional tick bar along the bottom edge.
This promotes that prototype into a real, versioned
@cypress-design/*component so it can replace the legacy notice everywhere (Overview, Specs,
and Test Results tabs) instead of living as throwaway page-local code.
Ported directly from the prototype, with one addition: a "scheduled to
complete" state (a trailing "60s remaining" pill + link to project settings)
for a run held open by
project.runCompletionDelayafter every groupfinishes — the prototype didn't have this, the old
NoticeSpecsQueuedRunningdid.
How
components/SpecResults/constants(@cypress-design/constants-spec-results,private) — the status model, class tables, and the one real algorithm,
buildSpecResultsView()(pill list + tick-bar groups, indeterminate /running / scheduled-to-complete / complete states).
components/SpecResults/react(@cypress-design/react-spec-results) —thin render layer: pills render as
<a>tags and, when mounted inside areact-router
Router, navigate client-side viauseNavigate(plainanchors otherwise);
@cypress-design/react-statusiconfor icons,@cypress-design/react-buttonfor the Cancel / Archive actions.architecture.md), since the only current consumer (cypress-services'dashboard) is React-only.
/components/SpecResults): theexisting
[component].astropipeline picks up the React-only package as-isand renders
react/ReadMe.md,instructions.md, and a live demo(
docs/src/demos/SpecResults.astro, one React island backed bySpecResultsDemo.tsx). An earlier revision shipped this as a hand-builtPattern page; reverted per review.
SpecResultCountsacceptsskippedandcancelledas separate inputs(matching Cloud's
NOTESTSvsCANCELLEDinstance statuses) that thecomponent itself sums into one "skipped" pill and one combined Specs-tab
filter link — callers don't pre-sum these.
[Npx]arbitrary value rather thanTailwind's rem-based scale (
h-6,text-base, ...) — a workaround for abug in the one current consumer (
cypress-services's dashboard still setsthe page's root font-size to 10px via a legacy
bootstrap-sassimport),not a flaw in this component or design system. Documented as a known issue
in
architecture.mdwith an explicit "don't switch back withoutconfirming the root cause is fixed" warning.
SpecResultsReact.cy.tsx) cover every documentedstate: indeterminate, running (partial/all-passing/queued-only), scheduled
to complete, complete (passed/failed/errored),
label="", and theskipped+cancelled combination.
Where to focus review
buildSpecResultsView()inconstants/src/index.ts— this is where allthe actual state logic lives; the React file is deliberately thin.
specs?specStatus=...,../../settings/general) — everyhref is relative to a Cypress Cloud run tab's own URL
(
/projects/:id/runs/:runId/<tab>);architecture.mdhas the reasoningand a worked example for why
specshas no leading../while thesettings link needs
../../.architecture.md, "Known issue"section) is the right call, versus fixing the root font-size bug in
cypress-services directly first.
Note
Medium Risk
New major UI surface for Cloud run pages with routing, filter URLs, and consumer-specific Tailwind workarounds; icon SVG changes affect all StatusIcon consumers.
Overview
Introduces
@cypress-design/react-spec-results(with private@cypress-design/constants-spec-results) as the versioned replacement for Cypress Cloud’s run-overview status strip: filterable per-status pills, proportional tick bar, optional Cancel / Archive, adescriptionslot, run-completion-delay (“scheduled to complete”) behavior, andbuildSpecResultsView()holding the state logic. Pills use client-side navigation when a React Router context exists; otherwise they stay plain anchors. Docs add component listing, agent index entry, architecture notes, a live demo, and broad Cypress coverage.Also insets the errored outline status icon circle (changeset patches icon-registry / StatusIcon packages), adds
action-archiveto the icon registry for Archive, documents thedist/rebuild gotcha for local component work, and addsreact-router-domas a dependency of the new package.Reviewed by Cursor Bugbot for commit 4a1121a. Bugbot is set up for automated code reviews on this repo. Configure here.