Skip to content

feat(website): add a "Modify entries" menu and fix the search action row layout - #6938

Draft
theosanderson-agent wants to merge 23 commits into
mainfrom
fix/download-submitted-data-button-layout
Draft

feat(website): add a "Modify entries" menu and fix the search action row layout#6938
theosanderson-agent wants to merge 23 commits into
mainfrom
fix/download-submitted-data-button-layout

Conversation

@theosanderson-agent

@theosanderson-agent theosanderson-agent commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Two things: the action row above the search results gets a "Modify entries" menu, and the layout problems that #6935 papered over are fixed at source. #6935 is reverted here.

Screenshots below are from this branch's preview deployment, with real submitted sequences.


1. A "Modify entries" menu

A group's released sequences page carried five buttons, two of them much the widest: "Edit data use terms (12,345 sequences)" and "Download originally submitted data (12,345 selected)". Both are rarely reached for, and both are shown only to a group's own members, so as standalone buttons they crowded out the controls everyone needs.

They now sit behind one button, which names its scope the way the download button beside it does:

Nothing selected

menu, all entries

With a selection

menu, selection

The submitted-data download is relabelled to say what it is for — editing those files and submitting them back is the reason to want them — and to distinguish a selection from the whole result set: "(1,234 sequences)" versus "(2 selected sequences)". It keeps its behaviour, including the tooltip explaining why it is unavailable above the 500-entry limit.

The released page's row goes from five buttons to four:

released row with selection

Supporting refactors

Both actions owned their own trigger button, so neither could be driven from a menu item:

  • EditDataUseTermsModal takes isOpen/onClose from its caller instead of owning a trigger, and exports editDataUseTermsLabel. The search row was its only caller.
  • The submitted-data download logic moves out of DownloadSubmittedDataButton into a useSubmittedDataDownload hook; the component is gone.

2. Layout fixed at source

The row overflowed on narrow screens. #6935 addressed that by letting it wrap; this fixes the four things that made it too wide in the first place.

DownloadSubmittedDataButton was not behaving like a button. It rendered as a fixed-width w-[18rem] button inside two wrapper divs, so it occupied a hard 288px regardless of content and formed its own layout box. Its wrappers existed to position a hand-rolled tooltip and an absolutely-positioned error box; the tooltip is now the shared HoverTooltip, and errors go through react-toastify like the rest of the app.

Buttons could not grow. h-10 fixed their height, so a label too long for its width wrapped inside the button and spilled outside its own border. The size classes now set a minimum height and vertical padding. Single-line buttons are unchanged at 40/32/24px and circular buttons keep their fixed dimensions; only a button whose label wraps is affected.

The download button was narrower than its own label. w-44 and w-60 were, by their own comment, "fine for up to two digit numbers" — "Download all entries" wrapped over two lines at every screen size, and longer counts were being clipped. They are now min-w-*, so the button still resists jitter as the count changes but widens when the label needs it.

The sequence count gave way far too late. sm:flex-row moved it onto its own line only below a 640px viewport, long after the row started overflowing. It keyed off the wrong thing: the search form sits beside this column from md up, so there is less room at a 768px viewport (448px) than at a 640px one (608px). flex-wrap now decides from the content, and the count takes basis-0 grow min-w-40 so the row breaks on how little space it will accept rather than how much its text wants — it narrows and wraps beside the buttons first. ml-auto keeps the buttons right-aligned once it does give way.

Public search page

1600px 1280px
1000px 820px
640px 430px

Released sequences page

1600px 1280px
1000px 820px
640px 430px

Buttons squeeze, but only so far

As the row narrows the buttons are squeezed rather than wrapped onto separate lines. Left alone that went absurdly far: at 430px "Customize columns" came out 13px wide and 260px tall, one letter per line.

before after

min-width: auto should have floored each button at its longest word and did not, because base.css applies word-break: break-word to every element. That legacy value permits a break between any two characters, and — unlike overflow-wrap: break-word — it does so when computing intrinsic sizes too, so a flex item's automatic minimum size collapses to the width of one character.

*:break-normal! on the row restores word-boundary breaking for the buttons, putting the floor back at the longest word: 69px rather than 13px. Squeezing below that is unaffected, and nothing overflows.

The important modifier is needed rather than a plain utility: that rule in base.css sits outside any cascade layer, and unlayered declarations beat layered ones whatever their specificity. Worth fixing at source separately — @apply break-words alone already stops long words overflowing, and the extra word-break: break-word only contributes the intrinsic-sizing behaviour that causes this, app-wide.


Testing

  • CI=1 npm run test — 709 passed, including six new tests for the menu: that it keeps its actions hidden until opened, that both appear when both apply, that each is omitted when it does not, and that the download names its purpose and scope for a selection and for the whole result set.
  • npm run check-types — 0 errors. eslint — clean.
  • Integration tests pass on CI (chromium, firefox, cli). download-submitted-data.spec.ts now opens the menu and clicks the item, via two new SearchPage helpers.
  • Every screenshot above was taken from this branch's preview at the stated width, on a group with real submitted and released sequences.

One thing worth noting for anyone running the checks locally: npm run format here reformatted playwright.config.ts in a way CI rejected, because the local install was prettier 3.8.4 where the project pins ^3.9.5 and the two disagree on wrapping a union type. Worth checking your prettier version matches before trusting a local format run.

Notes for review

The minimum-height change is in the shared buttonStyles, so it reaches every sized button in the app, not just this row. Single-line buttons are pixel-identical; it only takes effect where a label would otherwise have been clipped.

Accessibility, pre-existing. The over-limit state keeps the download item disabled, hence pointer-events-none and no keyboard focus, so the explanation of why it is disabled is reachable only by hovering with a mouse. Unchanged by this PR, but leaving it enabled and surfacing the limit as a toast on click would fix it — happy to switch.

PR Checklist

  • All necessary documentation has been adapted.
  • The implemented feature is covered by appropriate, automated tests.
  • Any manual testing that has been done is documented (see Testing).

🤖 Generated with Claude Code

🚀 Preview: Add preview label to enable

theosanderson and others added 2 commits July 21, 2026 13:12
`DownloadSubmittedDataButton` rendered itself as a fixed-width 18rem
button inside two nested wrapper divs, so it behaved as its own layout
box in the search action row rather than as a peer of the buttons beside
it. On the group released sequences page -- the only page that renders
it -- that pushed the row past the viewport on narrow screens.

Return the `Button` directly, matching `DownloadDialogButton`, and let it
size to its content. The two wrappers only existed to position a
hand-rolled tooltip and an absolutely-positioned error box:

- the tooltip is now the shared `HoverTooltip` (react-tooltip), applied
  only when the selection exceeds the download limit
- download errors now go through react-toastify, as elsewhere in the app

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@claude claude Bot added the website Tasks related to the web application label Jul 21, 2026
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This PR may be related to: #6662

@theosanderson theosanderson added the preview Triggers a deployment to argocd label Jul 21, 2026
Reverting #6935 wholesale went too far: with the button row back to a
plain non-wrapping `flex`, the buttons were squeezed narrower than their
labels. Because the labels then wrapped *inside* a fixed-height `h-10`
button, each button became three or four lines of text spilling outside
its own border, and the row still overflowed horizontally below 640px.

Absorb the narrowing in two stages instead:

- `flex-wrap` on the outer row drops the sequence count onto its own line
  as soon as the buttons stop fitting beside it, so they get the full
  width before they begin wrapping among themselves. This is driven by
  the content rather than a breakpoint, so it happens exactly when
  needed.
- In the button row, `whitespace-nowrap` (inherited by the buttons) stops
  any label wrapping inside its box, and `*:shrink-0` holds buttons at
  their natural width so they wrap as whole buttons.

Spacing is now the container's `gap` alone; the per-button `mr-4`/`ml-2`
margins that fought with it are gone, along with the leftover `ml-2`
wrapper around the submitted-data button.

Measured on a mock of the fullest possible row (all six buttons) with the
real compiled Tailwind: previously the row overflowed horizontally at
640px and below, with buttons rendering 3-4 lines of clipped text. It now
reflows cleanly -- count alone from 1280px, buttons over two lines from
1100px -- with no horizontal overflow until 375px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@theosanderson-agent theosanderson-agent changed the title fix(website): make "Download originally submitted data" a plain button fix(website): rework how the search action row responds to narrowing Jul 21, 2026
theosanderson and others added 7 commits July 21, 2026 13:39
Holding every label to a single line kept the row tidy down to about
480px, but below that the longest button -- "Download originally
submitted data (12,345 selected)", some 330px wide on its own -- could
not fit a phone viewport, so the row overflowed horizontally instead.
Wrapping that label over two lines is the better trade at that width.

Buttons could not previously wrap without looking broken: `h-10` fixed
their height, so a wrapped label spilled outside its own border. Give the
size classes a minimum height and vertical padding instead. A single-line
button is unchanged at 40px; one whose label wraps now grows to fit it.

In the search action row, replace the blanket `whitespace-nowrap` with
`*:max-w-full` alongside the existing `*:shrink-0`. Together these say: a
button keeps its natural width and never gets squeezed, but never exceeds
the row either -- so a label wraps only when the button genuinely cannot
fit on one line.

This surfaced a latent bug in `DownloadDialogButton`, whose fixed `w-60`
was, per its own comment, "fine for up to two digit numbers": larger
counts were being clipped by the fixed height. Making those widths
`min-w-*` keeps them steady against layout shifts, as intended, while
letting an over-long count widen the button.

Measured on a mock of the fullest row with the real compiled Tailwind:
no horizontal overflow at any width from 1440px down to 320px, every
button on one line except the longest, which wraps over two below 375px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reverse the order in which the action row gives up space. It previously
held every button at its natural width and reflowed the row around them,
so labels only ever wrapped as a last resort at phone widths. Wrapping a
label over two lines is the cheaper concession, so it should come first.

The row now degrades in four stages: buttons wrap their own labels; then
the sequence count moves onto its own line to hand them the full width;
and only then do the buttons break onto separate lines.

Getting that order needs breakpoints. Flexbox breaks a line at an item's
*unshrunk* width, so a wrapping row always puts buttons on separate lines
before it lets any of them narrow and wrap a label -- the two stages
cannot be sequenced by content alone. Hence `flex-nowrap` until the last
stage, and `*:shrink-0` is gone so buttons may narrow.

The breakpoints are container queries, not viewport ones, because the
search form sits beside this column from `md` up: the width actually
available here is not monotonic in viewport width, being narrower at a
768px viewport (448px) than at a 640px one (608px). No single viewport
breakpoint can express the staging -- one row of buttons is safe from
708-767px and again above 996px, a disjoint range. The container is
scoped to the action row alone so the result table is untouched.

Measured on the fullest row (six buttons) against real compiled Tailwind,
by container width: nothing wrapped above 1500px; labels wrapping with
the count still alongside from ~1430px; count on its own line with the
buttons still in one row from ~896px; buttons on separate lines below
768px. No horizontal overflow anywhere from 1800px down to 320px. The
common three-button public page never wraps a label at any width.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The count moved onto its own line at a fixed container width of 896px,
which was picked for the widest row that exists -- the five buttons of
the released-sequences page. A public search page carries two or three
much shorter buttons, so that breakpoint stacked the row while roughly
250px of empty space was still going spare.

Make that stage content-driven again: `flex-wrap` on the outer row keeps
the count alongside for exactly as long as the buttons fit beside it. On
a three-button row it now holds to ~700px of container rather than 896px;
on the five-button row it gives way earlier, as it must.

The button row keeps its `@3xl` breakpoint, which still has to be one:
flexbox breaks a line at an item's unshrunk width, so a wrapping row
would put buttons on separate lines before letting any of them narrow and
wrap a label.

An earlier attempt to keep both stages content-driven -- flex-basis of
min-content on the button row, so the count would give way only once the
buttons had been squeezed as far as they go -- is not used here: it made
the count's position non-monotonic in the container width (own line at
850px, back alongside at 768px), because min-content means something
different eitherside of the wrap switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lower the threshold at which the search action row gives up and breaks
its buttons onto separate lines, from 768px of container width to 512px.
Between those, the row now stays on a single line and lets the labels
wrap instead, which is the stated preference.

That was not previously possible: the widest row that exists could not
compress below 609px, so a threshold under it would have overflowed. The
whole of that floor is the download button's `min-w-60`, which alone
accounts for 609px against 473px without it. Make the floor conditional
on `@2xl` so it still holds the button steady against layout shifts at
the widths where there is room to notice them, and lifts once the row
needs to compress. The row's minimum then falls to 473px, comfortably
inside 512px.

The cost, accepted deliberately: labels get tall before the row finally
breaks. On the five-button released row the tallest button runs to three
lines at 768px and four from 640px down to the 512px threshold. Nothing
overflows horizontally at any width from 1800px down to 320px, and the
common public row never wraps a label above 640px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Undo the successive attempts at staging how the search action row
responds to narrowing, returning the row, the button styles and the
download button's width to exactly the state they had after #6935 was
reverted and the submitted-data button was made a plain button.

Keeps: the revert of #6935, and `DownloadSubmittedDataButton` rendering
as a plain button with its tooltip on the shared `HoverTooltip` and its
errors on react-toastify.

Drops: the container-query staging, the outer row's content-driven wrap,
the minimum-height button sizes, and the download button's `min-w-*`
floors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Modify entries" dropdown to the search action row, in the style of
the existing "Tools" menu, holding the two actions that change entries a
group has already released: editing their data use terms, and downloading
what was originally submitted.

Both are long-labelled, rarely reached for, and shown only to a group's
own members on their own released sequences, so as standalone buttons
they crowded out the controls everyone needs. On that page the row goes
from five buttons to four, two of which were the widest.

The submitted-data download is relabelled to say what it is for --
"Download 2 selected entries for bulk revision" -- since editing those
files and submitting them back is the reason to want them, and that is
more use than naming the format.

To let both actions be driven from a menu item, `EditDataUseTermsModal`
now takes its open state from its caller rather than owning a trigger
button, and the submitted-data download logic moves into a
`useSubmittedDataDownload` hook. Its over-limit tooltip survives the move.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@theosanderson-agent theosanderson-agent changed the title fix(website): rework how the search action row responds to narrowing feat(website): gather ways of modifying released entries behind one menu Jul 21, 2026
theosanderson and others added 11 commits July 21, 2026 15:20
Give the menu button the same treatment as the download button beside it,
so the two describe their scope alike: "Modify all entries" with nothing
selected, "Modify 12,345 selected entries" with a selection. A `min-w-44`
floor keeps it from jumping about as the count changes while still
allowing a long one, in the spirit of the download button's fixed width
but without its habit of clipping counts too long to fit.

Prefix it with the Pajamas admin icon, marked `aria-hidden` since the
label already says what the button does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`h-10` fixed a button's height, so a label too long for its width wrapped
*inside* the button and the text spilled outside its own border. Give the
size classes a minimum height and vertical padding instead. A single-line
button is unchanged -- 40px, 32px and 24px as before, and circular buttons
keep their fixed dimensions, since they must stay round -- and one whose
label wraps grows to fit it.

This already mattered for `DownloadDialogButton`, whose fixed `w-60` is,
by its own comment, "fine for up to two digit numbers": longer counts were
being clipped. Such a count now takes two lines in a taller button.

Drop `whitespace-nowrap` from the "Modify entries" button at the same
time. It carries a count too, and while the minimum height applied to it
already, its label could never wrap, so it would have overflowed
sideways instead of growing: in a 260px slot it ran to 331px wide rather
than wrapping to two lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The count gave way to the buttons only below a 640px viewport, by which
point the row had been overflowing for a long while -- it first does so
around 900px. `sm:flex-row` keys off viewport width, which is a poor
guide to the width this row actually has: the search form sits beside
this column from `md` up, so there is less room here at a 768px viewport
(448px) than at a 640px one (608px).

Let `flex-wrap` decide it from the content instead, so the count gives
way exactly when the buttons no longer fit beside it, whatever buttons
the page happens to show. Give the count `min-w-80` as well, so it claims
a little more width than its text needs and the two separate while the
buttons still have room rather than at the moment they stop fitting.

Measured, by viewport, with the search form's 288px accounted for: on a
public search page the count now takes its own line from 1100px rather
than 560px, and on a group's released sequences with a selection made,
from 1280px. Handing the buttons the full width that much earlier also
clears the row's overflow at 900, 800, 700, 640 and 560px on the public
page.

A container query with a fixed threshold was tried and dropped: sized for
the widest row it stacks the count on every laptop, and the widths the
row needs differ by 250px between pages, so no one threshold suits both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`min-w-70` rather than `min-w-80`. The count claims slightly less width
before the row splits; the viewport at which it takes its own line is
unchanged on both pages at the widths measured.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The count took its own line as soon as its text no longer fitted beside
the buttons at full width. Break the row on how little space the count
will accept instead of how much its text would like: `basis-0` makes the
wrap decision turn on its 160px floor, `grow` still hands it the space
going spare so it reads as one line at ordinary widths, and in between it
narrows and wraps beside the buttons rather than leaving.

By viewport, with the search form's 288px accounted for, the count now
stays alongside down to about 1000px on a public search page (was ~1120)
and about 1244px on a group's released sequences with a selection made
(was ~1360).

That is close to the floor. The stack point is the button row's width
plus whatever the count will accept, and the button row cannot compress:
the download button is a fixed `w-60`, the menu has a `min-w-44`, and the
links carry `mr-4`. So each pixel off the count's floor buys just one
pixel, at the cost of the count wrapping into an ever narrower ribbon.
Going meaningfully narrower means narrowing the buttons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the count on its own line the buttons were alone on theirs, and
`justify-between` places a lone item at the start, so they jumped from
the right edge to the left as the row wrapped. `ml-auto` holds them
against the right edge there. Above that width the count has already
grown into the space between them, leaving none for the margin to take,
so it changes nothing.

Measured on both the public and released rows: the buttons now sit flush
right at every width where they fit, wrapped or not.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Download data for bulk revision (3 sequences)", scoped in parentheses
the way the data use terms item beside it is, so the two menu items read
alike. Covers the singular, and falls back to "(all sequences)" when the
count is not yet known.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Download original data to prepare bulk revision (2 selected sequences)",
saying what the data is as well as what it is for, and distinguishing a
selection from the whole result set: "(1,234 sequences)" when nothing is
selected, "(2 selected sequences)" when something is.

Covers the new branch with a test. The `/for bulk revision/` matchers the
tests and the page object used no longer match the wording, so they now
look for `/bulk revision/`; one of them asserted the item was *absent*
and would have passed for the wrong reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`w-44` and `w-60` were narrower than the labels they held: "Download all
entries" wrapped over two lines at every screen size, and any count of
more than two digits did the same -- the fixed width was, by its own
comment, "fine for up to two digit numbers". Until buttons could grow
this was hidden, since the fixed height clipped the overflow instead.

Make them `min-w-*`. The button still holds a steady width against small
changes in the count, which is what the fixed width was for, but widens
rather than wrapping when the label needs it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Running `npm run format` reformatted this file as a side effect, and CI
rejected the result: the local install is prettier 3.8.4 where the
project pins ^3.9.5, and the two disagree on how to wrap the union type
for `trace`. The file is nothing to do with this branch, so put it back
as it is on main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ezed

Screenshots of the preview at 430px showed the released page's row
collapsing: with the row a plain non-wrapping `flex`, the buttons were
squeezed to their minimum width and "Customize columns" came out one
letter per line, 288px tall. That is the behaviour this branch inherits
from before #6935, and it would have shipped as a regression against
main, which wraps.

Let them wrap among themselves, with the container's `gap` replacing the
per-button `mr-4` and `ml-2` margins that fought with it. The wrapper
div left around the menu once its margin was gone is dropped too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@theosanderson-agent theosanderson-agent changed the title feat(website): gather ways of modifying released entries behind one menu feat(website): add a "Modify entries" menu and fix the search action row layout Jul 21, 2026
theosanderson and others added 2 commits July 21, 2026 17:26
Go back to squeezing the buttons as the row narrows rather than wrapping
them, but stop it short of the absurd: at 430px "Customize columns" was
coming out 13px wide and 260px tall, one letter per line.

`min-width: auto` should have floored each button at its longest word,
and did not, because `base.css` applies `word-break: break-word` to every
element. That legacy value permits a line break between any two
characters, and unlike `overflow-wrap: break-word` it does so when
computing intrinsic sizes as well -- so a flex item's automatic minimum
size collapses to the width of one character.

`*:break-normal` on the row restores word-boundary breaking for the
buttons, which puts the floor back at the longest word: 69px rather than
13px for that button. Squeezing is unaffected until then.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`*:break-normal` did not take effect: the `word-break: break-word` rule
in `base.css` sits outside any cascade layer, and unlayered declarations
beat layered ones whatever their specificity, so a Tailwind utility loses
to it. Verified on the preview, where the button was still 13px wide.

Use the important modifier, which does win.

The underlying rule is worth revisiting separately: `@apply break-words`
alone already stops long words overflowing, and the extra
`word-break: break-word` only adds the intrinsic-sizing behaviour that
causes this, app-wide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@anna-parker anna-parker removed the preview Triggers a deployment to argocd label Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

website Tasks related to the web application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants