feat(board): restyle the board page on the shared slice band - #971
Merged
Conversation
The seats were a component of their own — a vertical grid of rows that shared nothing with the band the esports pages are drawn in. They are slices of that band now. `BannerSlices` is `SliceBand` and takes a `layout`: `cover` is a game, where the art fills the slice, and `aside` is a person, where a face holds the left of the slice at its own width and stays there as the slice opens, so what grows is the room the words get. Collapsed, a name and a role sit on the face over a scrim; open, they step off it and the blurb arrives beside them. The strip above it stops stretching its photograph. Full-bleed was the defect: a window 2560 across asked for 2560 pixels of a photograph that has 1000, so the widest board was drawn at two and a half times its size, and a band a few hundred tall cropped a 1.5:1 group photograph to a quarter of its height. Given its own height and its own proportions the picture is about 530 across whatever the window is — every board downscales into that, and the whole photograph is shown. The strip keeps the width; the picture no longer has to cover it. The board's name, year, cheer and description move off the page and into the strip, beside the picture rather than under it, on a wash of the board's own colour that fades in over the picture's right edge. The wash is the island's own `--band-wash` tokens mixed with transparent, which is what the timeline lights its bands with, so the two agree and the page's ground still shows through. The copy stops explaining the page and stops inventing an election. Anybody in the association can ask to be on a board; there is no season to wait for and nothing to join first, so membership is no longer the way in that the closing band offered.
…hing opens The picture stands taller, which is also wider, because the photograph keeps its own proportions and height is the only dial. At 38vw it is about 57% of the window across at any size, which is the share that stops the strip reading as a line with a stamp at one end. The ceiling stops below the point where the smallest photograph the association owns would be asked for more pixels than it has. Both pictures now dissolve at the edge the colour arrives from. A wash subtle enough to see the page's ground through is invisible over a dark photograph, so however far the colour was pulled back over the picture the two still met at a line. Fading the picture itself is what the board rows on the old page did, and it is the join the strip wanted. A member's slice keeps its name where it was. The face holds the left of the slice and keeps the name, the nickname and what they were on it; what the slice grows is somewhere to the right for the blurb, on a fade of the island's own ground. Nothing is rearranged to make room, and prose is read on a flat surface rather than across a photograph. Only a member who wrote something opens. Twenty-five of the forty-six board members wrote no blurb and four whole boards wrote none between them, so a slice that grew onto an empty panel was the common case rather than the exception. An item in the band now says whether it opens onto anything, absent meaning it does, and every route into opening a slice asks: a pointer arriving, focus arriving, a click, and the slice the band settles on when it first draws. Where nothing in a band can open, it settles on nothing and stands still. The board's number and year are set large enough to read at a glance, and a second wash comes in from the top left and is gone by the bottom right, so the photograph is lit from a corner rather than having colour bolted to one side. The rank table had drifted from the seed, which its own two-way check caught: the third board's role is spelled out now rather than joined with a slash, and the two boards that wrote their commissioners in lower case have been given capitals. Those three spellings move to a table of what the rule must still tolerate rather than leaving the assertions behind, because the next board to invent a spelling will not check first.
The large Roman numeral was stamped across the lower left of the photograph, and the eyebrow beside the picture now reads BOARD IX · 2025-2026 a few centimetres away. It was the same fact said twice, and the louder of the two was said over the people in the group photograph. Its scrim goes with it. That gradient existed only so white would read on whatever the photograph happened to be underneath, and with nothing to make readable it was a wash over a board's own picture for no reason. The band no longer takes a number either: the numeral was the only thing that read it, and the eyebrow is composed in the domain from the same rule. A board with no photograph now has no picture box at all rather than a box of no width, and the strip carries its own height instead. That box was holding two absolutely positioned things, the centred numeral and the way to a photograph, and with the words moved into the strip the way in was sitting underneath them: the words panel is pulled back over the picture, so it intercepted every click on the plus. The plus stands in among the words now, where nothing covers it. Four boards have no name, no cheer and nothing written about them, so what they draw is the board's colour with the eyebrow in it. That is the design as it stands and it is left as it is rather than given a stand-in.
…dths The rules for the empty slice and its sentence had been pasted a second time inside the stacked block, which split the way-in slice's own rule across the brace and left the two of them applying on a phone alone. They already stand at the top level, so the copy goes.
…ssolve The timeline lights a stop with --band-wash. The banner is a far larger area and the same figures there read as a fill rather than a tint, so it gets its own pair beside them, heavier in light for the reason the existing pairs are. --photo-dissolve is a share of a picture's own width rather than a length, so the group photograph and a portrait a third its size read as the same softness.
The diagonal came off the photograph and into the words panel, where its 0% is the panel's own corner: nothing is drawn over the faces now but the dissolve at the picture's right edge. It is held clear of the pull-back and faded in at its left, or its own edge draws a line down that dissolve. The floor of the picture's height rises so a narrow window reads as a hero rather than as a line with a stamp at one end, and a board with no photograph carries the same figure. The words are read on the banner's own wash rather than the timeline's, and the eyebrow is set larger, since it is the line that says which board this is.
The band under it is a row of faces that each dissolve into their own wash, and a banner ending on a hard line above them read as two pages rather than one page.
The figure was written into the rule that grows a slice and into the comment beside the arithmetic that reads it. A band that wants its own pace can now say so in one place. Same 620ms as before.
…ide it A slice grew by a multiple of the row, so the row had to be re-divided and the picture was pushed into a column of its own to make the room: the face shrank to pay for the paragraph. It grows by what the words need instead. The band hands the stylesheet the width a slice has while nothing is open, the picture is that width open and the whole slice shut, which are the same figure, and the slices either side give up the difference. Given its own intrinsic width the picture came out half again as wide as the column the words were placed off, which is why the panel used to cut a face in half. The panel wears the banner's own recipe on the banner's own tokens, with no ground of its own, so the island's pattern carries through it and the two bands read as one page. It sits under the picture, so nothing is laid across a face: what reveals it is the picture's own dissolve, which is drawn on an open slice alone. Shut, six faces in a row are six faces in a row. The scrim under a name is cut to the foot of the slice and the glow with it. The faces are in the middle and the top of the frame, and a scrim tall enough to reach them is a filter over a photograph rather than a ground under a name. A face and a paragraph are read where a game's line-up is glanced at, so the band takes 950ms to open. The blurb is centred in its panel rather than held to 18rem from the top, which is what left a field of ground under three lines of text.
Two photographic bands meeting at a fade read as one picture bleeding into another however far the fade is drawn back, and made strong enough to divide them it dims the photographs it is there to join. A strip between them, with a mark on it, is a join a reader can see. The mark is the page's own: leaning ticks, the slant every button and chip is cut on and the one the accent dash above a name already draws, and a rule trailing off to the right. Its own ground rather than the island's, because a strip that shows the page's pattern sits between two areas that also show it, and a repeat lining up on one side of a join and not the other is what the strip is there to hide. It sits directly on the band below it. The banner's own foot fade is cut back to match: the strip is the join now, and the fade is only the last of the banner settling onto the page.
…s dark Given a ground near the bands' own it read as the same dark again with three marks floating in it, which is the busyness it was put there to end. What divides two bands has to be a thing of its own, so it is filled with the colour and the mark is knocked out of it in the near-black both themes keep for ink on a bright fill.
The wash reached its strongest at the far side of the panel, which is to say it deepened with distance from the thing it was lighting, and it took a whole element with a mask to keep its own edge off the photograph. One gradient does it now, from the picture's top right corner, which is the panel's own top left: it thins in every direction from there, so leftwards it is gone before the faces and rightwards the page's own ground takes over. Drawn from the corner rather than clipped to it, so there is no edge to hide and no element to mask. The carry under it does one job, giving the picture's dissolve a colour to fade into. Both figures come down: 4% and 10% dark, 7% and 16% light. A light source at one corner needs far less colour than a field of it did.
Thin, it read as a line with the page's own dark either side of it, and the pattern showing through those two dark bands is what still looked busy. A strip a mark can sit in divides the bands on its own, so the fade at the banner's foot has nothing left to do: the photograph runs to its edge and the colour takes over there.
The same decision the esports strip takes, and for the same reason: these bands are photographs, the dark treatment is what suits them, and a ground that follows the viewer only filters somebody's picture. Where the esports band gets away with the tokens alone, this one paints its own ground. Its panels are washes mixed with transparent so the island's pattern carries through them, and a token cannot repaint what is behind them: on a light theme the panels came out near white with white ink on them. The island's own texture, laid again over the dark ground. The timeline above and the call band below stay in the reader's theme, exactly as the esports pages leave them.
A stop may carry a colour of its own, and a board hands its own down. The line takes the colour of the stop under the pointer, then of the stop being read, and each band highlights in its own, so moving down a row of boards moves through their colours rather than watching one colour reach further along. Carried by the line's own stroke and its glow, both of which a browser interpolates, so the colour fades from one to the next in step with the lit stretch travelling and costs no animation of its own. Nothing changes on a strip whose stops have no colours, which is every strip today.
…stack A row of portraits at their own full height is photography and takes the dark treatment in both themes. The banner is one photograph beside the board's own words, and it reads as the page it is on, so it follows the theme the way the timeline above it and the call band below it do.
A hero is a photograph. Held to a photograph's height with nothing in it, the strip was a field of colour with a line of writing at the top, so the words decide the height now and the colour thins away downwards into the page rather than ending on a line, which is the join every photograph on the page makes.
Whole bands were being held dark in both themes, which darkened the page around the pictures as well as the pictures. It belongs to the picture and to what is drawn on it: the scrim under an overlaid name and the lift at the foot of a portrait take --color-void, near-black in both halves and what the theme keeps it for, and the name, the nickname and the role take near-white ink over them. Everything else follows the reader: the panel beside the picture is the page's own ground with the board's wash on it, and the description on it is the page's own ink. Open, the lift stops at the picture's edge rather than hazing the foot of that panel. A band whose members have no portraits has no photography in it, so it keeps the reader's theme, and it is as tall as the names in it rather than as tall as a face.
Near-black is right in the dark half and reads as a hole cut in the page in the light one, so --photo-scrim takes it up a few steps there. Its own token rather than --color-void, which is ink on a bright fill and is the same in both halves by design. The near-white name over it still clears contrast several times over.
A motivation letter over email is the way in and the window runs March to May, which the band never said, and the Discord line stands all year, which is what keeps somebody wondering in October from reading this as a door that is shut. The election is not mentioned: it is a fact about a process nobody asked about.
The handover month is a fact about a calendar. That a board year runs with the academic one is the thing a reader needs, and what the boards try to do with it, keeping the place open and welcoming and putting on events where gamers meet one another, is what the page is an invitation to. The line about each board's own name, colour and cheer goes with it: the page below is made of exactly those, so saying it first is telling a reader what they are about to look at.
…icture Filled with its own mix of the colour and the pit it read as a block of paint with writing on it, while the same board beside a photograph read as a tint: two different pages. The banner's own recipe instead, on the same tokens and mixed with transparent, so the page's ground and its pattern come through. The corner is the strip's own top left, since there is no picture for the light to come off.
…years The page is not an index of years, it is the boards and what they built, so the heading says so. Two lines still, since the second takes the colour.
…knames Six seats on the candidate board, with a name, a nickname and a role each and nothing else: nobody has written a blurb or sat for a portrait yet, which is what the page draws a board with no art as. The seventh board's nicknames were never recorded, and board three's roles were written in three different capitalisations with one of them reading Secretary/Treasurer, which is two seats in one field rather than the one seat it was. The seed test said the tenth board had no seats and counted forty-six of them. Both were facts about the file rather than rules about it, so both move with it.
House rule, applied to every file this branch touches: a comma, a colon, a full stop or brackets say the same thing. Sixty-seven in the frontend's own comments and copy, plus the ones in the e2e and unit specs. The en-dashes in the board seed stay where they are: those are board members' own words about themselves and not ours to edit. Three comments go with them. The band's style block opened with two blocks that the code had since contradicted, one saying the band was the same height whether or not it had a photograph and one saying it sat in the island's column rather than edge to edge. The open roster carried two descriptions of itself, the older one about a margin and a cap that no longer exist. The empty slice carried two comments belonging to the way-in slice beside it, which has its own.
Choosing another board on the line sends the one on screen off the way the reader is going and brings the one they chose in from the other side. Back down the line the board on screen leaves to the right and the older one arrives from the left, and forward is the mirror of it. The line itself stays where it is, since it is the thing being travelled along. The esports pages have done this for a while, so this is that mechanism rather than a second one. It comes out of SeasonSwipe into BandSwipe beside the other island components both pages share: the pass, the two stops on the page at once, the height carried from one to the other, the stop leaving turned into a picture of itself so nothing tabs into it or finds two slices under one name, and the crossfade under prefers-reduced-motion. The esports side keeps the season vocabulary, its own class and its own testid, and hands the island a direction. Which of two stops is later stays the domain's answer. Boards compare the terms they ran, which is the same comparator the line is drawn in, so the order it reads in and the way a change travels are one question asked twice.
…ce opening The blurb's box was pinned to the slice's right edge, so it widened with the slice and the prose was laid out again on every frame: three lines becoming two becoming one, a text layout a frame across the whole band. Its left is the picture's edge and its width is the room the words were given, both of which stand still, so the box never changes and the growth is a layout of the row rather than of the row and its prose. It also arrives after the growth instead of during it. Prose fading in under a box that is still moving is two things happening at once, and the words are the slower read of the two. Three more, all cheap: the lift at the foot of a portrait drops its 26px blur, which was a second pass over a gradient already that soft and six offscreen renders a frame while the row was moving; a slice contains its own layout and paint, so one slice moving is not six slices painting; and a portrait decodes off the main thread, which is where a board change spends its one long frame, mounting a second complete band. Not the group photograph: the hero is what the page is, and a hero that paints a frame or two after everything else is a hole where the board should be. Measured at four times CPU throttle: hovering across the faces holds 60fps with no frame over 25ms, scrolling and idling likewise, and a board change costs a single frame at the swap.
Fugaz One has no İ, ı, ş, Ş, ğ or Ğ, so a display face on a site whose members are named in them is a face with holes in it: that is why a name has never been allowed to use it, and why --font-name exists at all. Fugaz One TR is the same face with those letters cut, so the whole of the site's display type can be set in it. The licence sits beside the file as FugazOneTR-OFL.txt, verbatim, the way the SIL Open Font License asks. The face it replaces goes: two nearly identical display faces in one directory is a question nobody should have to answer twice. --font-name still names Barlow. What kept a name out of the display face was a correctness constraint and is now a preference: a name is read, and a display face is shouted. Both comments say so, and the glyph test now holds both faces to covering the letters rather than recording which of them did not.
It is our modification of somebody else's font, and the licence has a rule about that: "Fugaz" and "Fugaz One" are Reserved Font Names, and clause 3 of the SIL Open Font License is that no modified version may use them as the name it presents to users. Calling it Fugaz One TR was using one. So the face is Shellhouse One, in the font's own name table as well as in every font-family rule: family, full and PostScript names, and Blueshell as the manufacturer, since we made this cut. What the licence does ask for stays exactly where it was: the original copyright notice with both reserved names in it, the licence record, and the designer credit to Daniel Hernandez, whose outlines these are. The description in the file says what was added and that neither LatinoType nor he is affiliated with it or endorses it. Beside the font: ShellhouseOne-OFL.txt, the licence verbatim, and ShellhouseOne-NOTICE.txt, which records the six characters added, that they are composed from the original's own outlines, that nothing else was changed, and why the name is what it is.
It ran past the picture and onto the panel, where the lift under it stops and the ground it is read against is the page: near-white ink on a pale panel in the light half. It wraps at the face's own width now, which is where its ground ends.
Boards are not all the same height, and a call band that stayed put while the board above it travelled was the one thing on screen saying nothing was moving. It sits inside the pass now, so what leaves and arrives is the whole board and everything the page says about it.
A slice opening is a row's layout animated over most of a second. The arriving band settled on its own the moment it mounted, which is in the middle of a pass, so that animation ran inside a subtree being translated across the screen, and ran twice: the board leaving is still on the page with a slice of its own open. That is the stutter. The swipe now says whether a pass is on and the bands read it: a band settles on the slice it opens once the page has stopped, and answers no pointer until then, since whatever is under the pointer mid-pass is not what the visitor was reaching for. Injected with a default of not travelling, so a band outside a swipe behaves exactly as it did. Counted mid-pass, before and after, as open slices in the leaving band over open slices in the arriving one: 1/1 became 1/0.
…ning It waited for the growth to finish, which it no longer has to: what made waiting worth it was prose being re-wrapped on every frame, and the box it is laid out in stands still now. So it begins a third of the way in and is there before the slice settles. A third rather than half: the easing is front-loaded, so at half way the slice is already within a few pixels of its width and a fade starting then reads as arriving afterwards. Measured at 285ms the slice is at 438 of 528 and still moving.
… without a face The words panel is pulled back over the picture so the words can sit close, and its wash came with it: at the top, where the photograph is most solid, the colour lay across it and its own edge read as a line down the picture. Drawn back to start at the picture's edge instead, it left a strip of bare ground between the two, which was worse. So the wash is a layer of its own, spanning the pulled-back panel and masked to nothing where the photograph is still solid, at full by the picture's right edge. That stretch is the picture's own dissolve, so the colour comes up exactly as the photograph goes down: no line on the picture, no gap beside it. A little of the colour runs along the top as well, faintly, so the corner is where the light is strongest rather than the only place it is at all. A slice with no portrait loses the scrim under its name. There is no photograph beneath it, so what it drew was a dark fade up the foot of the panel and nothing else. Per slice, since a board may have portraits for some of its members and none for the rest.
…photograph's ink The band knows whether a slice has art, so it says so: a bare slice carries a class, and the two things that only make sense over a photograph read it. The scrim under the name goes. With no picture beneath it, what it drew was a dark fade up the foot of the panel, which in the light half is a grey band across a pale page. The near-white ink goes with it. It is the ink a name needs on a photograph, and with no dark ground under it, it is near-white ink on the page: a bare slice takes the theme's own ink, the way the description beside it already does. Said as a class rather than asked with :has(), and this is why: the rule that asked was written and committed and never matched anything. What a slice is made of is something the band knows at render time.
…un the other way The mark was taller than the line and read as a separate thing beside it. Same weight now, and more of them, each dash a little longer than the last and each closer to the next, the last one running to the edge and handing straight over: what a reader sees is one line that begins as dashes and gathers into a rule. And it takes a mirror. A band closed with the same mark it was opened with reads as the same rule drawn twice; the other way about, the two hold the band between them. A flip rather than a second set of gradients, since there is nothing in here that reads.
It was opened with one and closed with nothing, so it ran into whatever followed it. The rule under it is the same one mirrored: dashes at the right, the line off to the left.
What opens a band is that band's own colour, the board's on the board page. What closes it is the site's, on any page that closes one, so the mirrored rule carries the blue with it rather than saying the board's colour a second time at the foot.
A board's colour is the board: the line it sits on, the banner, the wash beside its photograph. A rule holding a band is the site drawing a join, and it says so in the site's own colour at both ends rather than repeating the board's. The mirror is about which way it runs again, and nothing else.
…s a shift A game's banner sits at 1.06 shut and comes back to 1 open, which is a slow push on a landscape. On a row of faces it read as the wrong thing entirely: moving the pointer along, the face being left zoomed in while the face being reached zoomed out, so what should be one shift across the band was two zooms in opposite directions. A face is also scaled about its own centre, so the framing drifted vertically while it happened. One level now, open or shut, and the only thing that animates is the picture's width. Measured across a hover: every portrait at scale 1, every box the same height at the same y, and the widths the only figures that move.
The url was one directory too high: from src/components/island, ../../../assets is outside src altogether. In dev the request fell through to the app's own index.html, so the browser was handed a page where it wanted a jpeg and painted nothing, leaving the flat blue underneath it. The pane has looked like that on both bands that carry one. Caught by the build, which resolves what the browser only shrugs at: it emits /src/assets/bg/shelly-bg-blue.jpg now, and that answers with an image.
…o anything A pointer and a click asked. The scroll that decides on a phone, the slice named from outside after something is added, and the slice a rebuilt band settles on did not: each set the open index directly, so a member who wrote nothing could be opened onto an empty panel, which is the defect the expandable rule exists to prevent. Scrolling past such a member on a phone did it every time. One function answers it now and all five routes go through it. Where the answer is no, the scroll leaves what was open open, the named slice is not opened, and a rebuilt band falls back to the first slice that does open.
They asserted the structure the restyle replaced: a numeral that is gone, per-seat ids that went with SeatRows, monograms and chevrons that no longer exist, a membership action the call band dropped, and an identity block that now lives inside the banner. Roughly thirty assertions that could not pass. Rewritten against what the band renders. A name and a role are read through the slice, since they are drawn on the face and have no ids of their own; a portrait is the slice's own picture; a member with no portrait is a slice with no picture rather than a monogram; and whether a slice is open is read from its class, because a shut slice keeps its words in the document and gives them no room. Three things the rewrite pinned down rather than assumed: a board with no photograph is shorter than one with a photograph, a cheer is drawn inside quotation marks, and a second press does not shut a seat, another seat opening does. 52 pass across chromium and mobile-chrome.
Four cases asserted forty-six seats, five nickname-less seats on the seventh board and none at all on the tenth. The seed has fifty-two seats now, the seventh board's nicknames are recorded and the tenth is seated, so all four failed, and they were the API job's failure on this branch. The numbers come out of the files instead. What is under test here is the loader: that everything the files hold lands, once each, and survives a second run. A number typed beside the assertion tests the file rather than the loader, and rots the day somebody seats a board, which is exactly what happened. The files' own facts stay asserted in BoardSeedParsingTest, where a change to them is supposed to fail. The nickname case now counts every seat with none across the whole seed rather than the seventh board's five: which boards those seats are on is the file's business. 24 tests pass against a real MariaDB.
…aying for the cold start A shard failed on `page.navigate` exceeding five seconds against /membership/signup, and the next test in the same class passed in about a second. That is the cold start: `docker compose up --wait` calls a container healthy as soon as its own check passes, which is before nginx has served a page or the api has answered anything, so the first navigation of the suite pays for a cold server, a cold JVM and a first render, and whichever test ran first was charged the lot. So the suite waits for it, once per JVM, before any test runs: the api until /health answers, the frontend until it serves a page, then one throwaway page load that pays for the bundle and the fonts. The five second ceiling stays exactly where it is, because it is still true that an *action* needing longer is waiting on the wrong signal. Two things this turned up while being verified locally, both of which would have made it worse than what it replaces: - Java's HttpClient defaults to HTTP/2, which over cleartext asks for an h2c upgrade the frontend's server never answers: every probe of an origin that serves 200 to curl in two milliseconds timed out, for the full ninety seconds. Pinned to HTTP/1.1. - The api's health endpoint is /health, not /actuator/health, which 404s. A 404 would have satisfied a check written as "answers with anything under 500", so the gate would have passed without the api being up at all. And navigateWithRetry goes: three attempts with a sleep between them, for the window after compose reports healthy while ports are still binding. Nothing called it, and the gate is that window's answer.
The page still declares --accent-ink, the board's colour mixed towards the theme's own so a pale colour reads on a pale page and a dark one lifts off a dark page. Nothing read it: the rule that did names .board-cheer, and the cheer moved inside the band when the page was rebuilt, where a scoped rule in another component cannot reach it. So the band reads it, with the ink of whatever it is in as the fallback, and the dead rule goes. Two end-to-end cases were asserting this all along, one per theme, and would have passed against plain chalk without noticing the colour had gone.
The same stale structure boards.spec.ts had, in the two specs that drive the pencils: the board's own words under their old ids, per-seat names and roles under ids that went with SeatRows, a monogram for a seat with no portrait, and a plate whose src was read to prove a cancelled dialog changed nothing. Forty-two failures across the two files, every one of them a locator that resolves to nothing. Read through the slice now, since a name and a role are drawn on the face and have no ids of their own, and through the slice's own picture for a portrait. A seat with no portrait draws no picture rather than initials, which is what the two upload cases now assert either side of the save. One case was asserting the wrong reader: a board with no seats shows a visitor a paragraph saying so, but shows somebody who may seat people the band with the way in at the end of it, so what is empty is a slice in the row. It was logged in as a board member and waiting for the paragraph. 110 pass across the three board specs in both projects.
…ing it One case failed on the phone projection under load and passed alone, which is the shape of a race rather than of a slow machine. It is a real one, and it belongs to the test. Stacked, the scroll decides which seat is open, and a scroll releases a tap by design: the choice stands until the visitor scrolls, at which point the scroll is their intent again. Playwright scrolls an element into view as part of clicking it and that scroll event is delivered asynchronously, so a press can be undone by its own scroll arriving after it. Whether it does depends on how loaded the machine is, which is why it came and went. Scrolling first and pressing second is both deterministic and the order a finger makes. 156 runs of the file, three passes over both projections at six workers, all green.
…cking it The last e2e failure, and the same fault as the seat press one commit ago wearing different clothes. A pencil is hidden until its slice is hovered. A click scrolls its target into view first, that scroll takes the slice out from under the pointer, the hover goes with it and the pencil disappears: 'element is visible, enabled and stable', then 'scrolling into view if needed', then 'element is not visible' eight times over until the budget ran out. Load decides whether the scroll was needed at all, which is why it came and went. Both helpers scroll first, hover second, wait for the pencil, then press it. A slice already in view has nothing to scroll. 99 runs green: both editor specs, two passes over both projections at eight workers, which is what CI uses. The same shape is still in esports-game-edit, esports-game-remove and esports-lineup-edit. None has flaked, so none is changed here.
ExtraToast
added a commit
that referenced
this pull request
Sep 2, 2026
… by pressing it (#972) The board specs carried this fault and it cost two rounds of red CI on #971: a pencil is hidden until what it belongs to is hovered, a click scrolls its target into view first, and that scroll takes the element out from under the pointer, so the click loses the hover it needs and reports the pencil as not visible. Whether the scroll is needed at all depends on where the page is sitting, which is what made it come and go. The esports specs have the same shape in four places and have not flaked, which is luck: their bands are shorter and the target is usually already in view. Scrolling first, hovering second and pressing third costs nothing and is the order a hand makes. 48 pass across the three files in both projections.
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.
Implements #970.
What changed
The board page now uses the same band component as the esports pages.
SliceBandgained alayoutprop:coverfor games (art fills the slice) andasidefor people (a portrait holdsthe left of the slice). Hover behaviour, how much a row an open slice takes, touch behaviour and
image sizing are shared rather than duplicated. This deletes
SeatRows.vue(481 lines) andBoardManager.vue(647 lines), the two components the island page replaced.Board member slices. The portrait sits on the left at full height; the name, nickname and
role stay on top of it whether the slice is open or closed. Opening a slice widens it and shows
the member's description to the right of the photo. Only members who actually wrote a
description can open, so the six early boards (which have none) render as a static row.
The group photograph is shown in full. It keeps its aspect ratio and is sized by height, so
it is never scaled beyond its own resolution (the old band stretched a 1000px-wide photo across
a 2560px window). Its right edge fades into the board's colour instead of ending on a hard line.
Boards without a photo render as a short strip sized to their text, not a full-height empty one.
Switching boards slides. Picking another board on the timeline slides the current one out
and the new one in, in the direction you're travelling in time; the timeline itself stays put.
This reuses the esports page's existing slide mechanism, extracted from
SeasonSwipe.vueintoBandSwipe.vueso both pages share it.SeasonSwipeis now a thin wrapper. Slices don't openduring a slide, which previously animated two rows of layout inside a moving element.
Divider lines (
BandRule.vue) above and below the member band, in the association blue,drawn half over each band so they read as a join rather than a third band.
Timeline colours. A
Stopcan now carry its own accent colour, and boards pass theirs down,so hovering a board fades the timeline's lit segment to that board's colour. No board has a
colour in the database yet, so everything currently renders in the association blue; nothing
hardcodes a colour.
New display font. Fugaz One has no İ, ı, ş, Ş, ğ or Ğ, so member names in those characters
lost letters. Replaced with our own modified cut containing them, renamed to Shellhouse One
because the SIL Open Font License forbids modified versions from using the original's reserved
name. The licence and a notice of what changed ship alongside the font.
Seed data. Board 10's six members (name, nickname, role only — no descriptions or portraits
yet), board 7's nicknames, and board 3's role capitalisation.
BoardSeedParsingTestupdated:it asserted board 10 had no members and counted 46 rows.
Copy. Page heading, intro paragraph and the "put your name forward" call band rewritten:
board years follow the academic year, applications are by motivation letter over email between
March and May, and Discord is open all year.
Testing
Frontend unit suite passes (137 files, 1047 tests). Typecheck and lint pass. The end-to-end
board spec is rewritten in this branch: the old assertions targeted
SeatRows' DOM(
board-seat-name-*,board-seat-monogram-*,board-numeral), none of which exists now.Visual details — gradient stops, fade widths, animation timings — were tuned against
screenshots rather than asserted, as #970 records. Two things were measured rather than eyeballed:
to the slice's growing right edge; it now has a fixed width).
1/1to1/0.Known gaps
members. Rename is a separate change.
awkwardly. Left for a separate mobile pass.
object-fit: coverrecalculates against the box it is given. Fixing it by sizing on height instead leaves a
gap wherever a slice is wider than a portrait (measured: none at 1440px with six members,
45px at 2560px, 192px at 3440px). Deliberately parked.
Notes for reviewers
team-slice*even though it now draws board members.Nine e2e specs select on those classes and are the evidence the esports pages didn't change,
so they weren't renamed alongside the refactor.
island.css:--photo-scrim(the overlay behind a name on a photo, thesame darkness in both themes, because that text is white in both), and
--board-wash/--board-wash-on(the banner's colour wash, weaker than the timeline's because it covers amuch larger area).
url()pointed one directoryabove
src, so the dev server returnedindex.htmland the browser rendered nothing. Bothbands using that pane were affected.
Diff breakdown —
█added░removed, scaled to the largest row.