Skip to content

feat(board): the boards read as a timeline - #956

Merged
ExtraToast merged 1 commit into
mainfrom
feat/board-page-timeline
Sep 2, 2026
Merged

feat(board): the boards read as a timeline#956
ExtraToast merged 1 commit into
mainfrom
feat/board-page-timeline

Conversation

@ExtraToast

@ExtraToast ExtraToast commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What this does

/board is an island page. Top to bottom: an island header, the timeline of every board, the
board band, the identity block, the seats, the closing band. The Vuetify TopBanner is gone, and
so is the wall of grey toggles behind it.

  • The timeline carries every board, oldest at the left, each stop labelled with the board's own
    name — composed from its number in Roman numerals where none is recorded — and the academic year
    beneath it. The board in office is marked In office, a board elected and not yet sitting
    Candidate. The lit stretch ends on the board being read.
  • The url follows. Choosing a board pushes ?board=<number>, so a board is linkable and the
    back button walks back through the years. Absent a board in the url the page opens on the board
    in office; a url naming a board nobody recorded falls through to it rather than to a blank page.
  • The band is the board photo edge to edge, cut top and bottom on the island's own diagonal,
    the number set large in Roman numerals at the lower left and shaved by the lower cut, with a
    scrim under the numeral only rather than over the photograph. A board with no photograph keeps
    the band and its height, filled with its own colour and the numeral centred — boards I to IV have
    none and board X has none yet, so that is the normal case for half the history.
  • The identity block reads BOARD IX · 2025-2026, then the name, then the cheer in the display
    face and the accent, then the description in body text.
  • Seats still render through BoardMemberRow, ordered with seatsInOrder and named with the
    nickname in place (seatTitle in the adapter already did that). Seats read as expandable rows #931 replaces the rows.

Where the in-office rule went

src/domains/boards/standing.ts, beside the other reading rules, importing nothing:

  • isCandidate(board, on?) — the term opens later than today.
  • boardInOffice(boards, on?) — the board whose term contains today; where none does, the newest
    board that is not a candidate answers instead. Null where there are no boards, or where every
    board recorded has yet to begin. A board with no readable start date is passed over.
  • standingOf(board, boards, on?)"in office" | "candidate" | "past", read against the whole
    line, because being in office is a fact about the line rather than about one board's dates.

tests/unit/domains/boards/standing.test.ts reads it against the real seeded terms, which is where
the awkward shapes live: board IX runs to 16 September and board X opens on the 17th, so the tidy
"1 September to 31 August" fixture never meets either case. boardAxis.ts turns boards into the
timeline's stops and boardInRoute.ts reads ?board=; both are unit-tested too. The page asks; it
works nothing out.

Stop in the shared island gains an optional mark — a word marking one stop out from the line.
What the word is stays with whoever draws the strip, so the esports timeline is unchanged.

The two things #930's comment recorded

  1. ImagePicker's contract. Nothing in this ticket picks a picture — the dialogs are A board is edited on the page #932 and
    A seat is edited on the page #933 — so no FileType is reached for anywhere new. The wiring the comment points at is intact:
    storeBoardPhoto and storeSeatPortrait are exported from the boards adapter as PictureStore
    and are what those dialogs bind to store.
  2. Two spellings of a board's fallback name. The page reads boardName (Board IV). The
    adapter's boardTitle is gone and its digits went with its last caller, as a local helper in
    BoardManager.vue, which The board editor leaves the management area #934 deletes.

Copy — DRAFT, awaiting your approval

Both of these need redlining. Quoted exactly as committed.

Island header

eyebrow: Blueshell Boards

headline: Every year of the association, / and who ran it (second line in the accent)

intro: A board runs Blueshell for one academic year and hands over in the autumn. The line below is every board the association has had: choose one to read who sat on it, what it called itself and what it shouted. The board in office is marked, and it is where this page opens.

Closing band (src/domains/boards/island/boardCall.ts)

headline: Stand for the board

body: A board year is a year of running Blueshell: the events, the money, the members and the games. Elections are in the spring and the handover is in the autumn, so the board in office is the one to ask what the year is really like.

actions: Become a member/membership (solid) · Ask on Discord → the association's invite · Ask over emailboard@blueshell.utwente.nl (quiet)

Two smaller strings, same standing: No boards are recorded yet. and No seats are recorded on this board yet. (the second is what board X shows today).

Three judgement calls worth a look

  1. A blank name renders nothing in the identity block, while the timeline still composes
    Board IV. The two acceptance criteria read against each other — "each stop shows the board's
    name, composed from its number where it has none" and "a blank name, cheer or description renders
    nothing rather than a placeholder" — and this is the reading that satisfies both: a stop may not
    be blank, and a heading repeating the eyebrow that has just said BOARD IV is the placeholder.
  2. The band draws photo and does not fall back to image. The old page drew
    asset(board.image), but the band's contract is a Picture with a width ladder, and an asset
    path has neither a width nor a rendition — so a fallback could not satisfy "a large photograph
    does not cost a reader the page". Every seeded board with a photograph carries both fields, so
    nothing loses its picture. Seat portraits keep the fallback, because that row takes a bare
    url: stored picture first, then the assets directory, until The asset file names go #935.
  3. "Candidate" collides with the vestigial candidate column, which holds a copy of the name and
    which nothing reads. The word is the one the ticket asked for and the standing is derived rather
    than stored, so nothing is ambiguous in the code — but if the column outlives A board is edited on the page #932 it may be worth
    renaming one of the two.

Accent

Every board draws the association's blue today, because no board has a colour recorded. The page
reads one value into the lit stretch of the timeline, the band, the bare band's fill and the cheer,
so a colour appearing on a board needs no further change. It is made readable rather than used raw:
--accent-ink mixes the accent towards --color-chalk, which is near-white in the dark half and
near-black in the light one, with the heavier hand in light — so a pale historical colour drops onto
the pale ground instead of vanishing into it. Declared on the element that carries --accent, since
a custom property built out of another is substituted where it is declared.

island.css now scans pages/Board.vue and domains/boards, without which none of the page's
Tailwind exists.

Verification

command exit
yarn typecheck 0
yarn lint --max-warnings=0 0
yarn test:unit 0 — 986 passed, 135 files
yarn test:e2e --grep "board" 0 — 62 passed, 2 skipped
yarn test:e2e (full) 1 — 632 passed, 3 failed

The three full-run failures are all page.goto timing out at 5s in one worker window
(navigation-links ×2, home-competitive-games ×1); the mobile-chrome copies of the same tests
passed in the same run. Re-run in isolation: yarn test:e2e tests/e2e/navigation-links.spec.ts tests/e2e/home-competitive-games.spec.ts → exit 0, 18 passed. Runner starvation, not this change.

New end-to-end coverage: the stops and their marks, choosing a board and the url following, the back
button, the Roman numerals, the band with and without a photograph (including which copy a phone
actually fetches), the candidate board appearing without being focused, a blank cheer, description
and name rendering nothing, the seat order and the nicknames, and the three blocks stacked on a
phone. The board manager half of boards.spec.ts is untouched and still passes — #934 removes it.

Closes #930


Diff breakdown added removed, scaled to the largest row.

frontend                                         +1424   -276   18
  production         █████████████████████░░░░░   +805   -193   13
  unit tests         █████████░                   +317    -48    3
  e2e tests          ████████░                    +302    -35    2

──────────────────────────────────────────────────────────────────
production                                        +805   -193
tests                                             +619    -83  0.77 test lines per prod line
total (hand-written)                             +1424   -276  18 files

@ExtraToast ExtraToast added the ready-for-agent Sized and specified for an agent to pick up label Sep 1, 2026
@ExtraToast
ExtraToast merged commit 1ae76a9 into main Sep 2, 2026
25 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-agent Sized and specified for an agent to pick up

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The board page reads as a timeline

1 participant