Skip to content

Cut 0.14.0: the board's spacing as properties, a ceiling on col-auto pickers - #69

Merged
FourCourtJester merged 20 commits into
mainfrom
claude/repo-overview-bcfbgu
Oct 23, 2026
Merged

FourCourtJester merged 20 commits into
mainfrom
claude/repo-overview-bcfbgu

Conversation

@FourCourtJester

@FourCourtJester FourCourtJester commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Three findings from a studio running 0.13.0 on client work, fixed and released together as 0.14.0. Each was measured on a live board there and checked against the code here.

The board's spacing is the studio's to set

The board stepped its padding at 640px through Tailwind classes written into core's markup:

Element Was
each panel p-3 sm:p-4
the board's <main> gap-3 p-3 sm:gap-4 sm:p-4
the top bar px-3 py-2.5 sm:px-4 sm:py-3

A studio could only undo them by writing CSS against those classes. The <main> had no class at all.

Now:

  • Five properties on .ss-control: --ss-panel-pad, --ss-board-pad, --ss-board-gap, --ss-bar-pad-x and --ss-bar-pad-y.
  • By default they keep the same values and the same step at 640px.
  • The <main> is .ss-board.
380px 1280px
left alone (panel / board / gap / bar x / bar y) 12 / 12 / 12 / 12 / 10px 16 / 16 / 16 / 16 / 12px
a studio sets 20px on .ss-control 20px everywhere 20px everywhere

A col-auto image picker has a ceiling

col-auto never shrinks a control. An ImagePicker is a <select>, so it's as wide as the longest image name in the library. In the studio, a logo named northwind-esports-primary-logo-2026 made the picker 365px and squeezed the field beside it to 25px.

  • The fix: col-auto now caps an image picker at --ss-picker-max, which defaults to 9.25rem.
  • The cap lives inside the utility itself. So sm:col-auto only caps from 640px up, and below that the picker still has its line to itself. A studio couldn't write that rule, because the sm:col-auto class sits on the element at every width.
  • Measured with that long name in the library:
    • at 1024px, the picker is 148px and the field gets 798px of a 958px row;
    • at 380px, the picker fills its line, 330 of 330px.

Docs

  • getting-started:
    • a table of the spacing properties;
    • read an --ss-* property with a fallback, var(--ss-basis, 0%), since a misspelt or renamed one silently means no rule;
    • a Troubleshooting entry for a dev server serving half a stylesheet. If npm run build is right, restart the server.
  • releasing.md: renaming an --ss-* property is a quiet breaking change.

CI stops stalling on the Chromium download

The "Install Chromium" step hung three times on 2026-10-23, 7–10 minutes each against 20–27 seconds healthy, and each time held a PR until someone cancelled it.

  • The browser is cached, keyed on Playwright's version (actions/cache@v5), so most runs don't download it at all.
  • Each install attempt gets two minutes and three tries. A stalled attempt is killed along with its child processes, then retried.
  • Time limits: verify 20 minutes (normally about 4), and the Pages jobs 10 minutes each.
  • Checked locally: the retry loop, against a fake installer that hangs. It succeeds on the third attempt when the first two hang, fails after three when every attempt hangs, and kills each hung attempt's child process.

Release

  • All six packages move to 0.14.0.
  • The plugins' peer range, both templates and the demo move to ^0.14.0.
  • A minor version, because a col-auto picker can now render narrower, and the board's spacing moved out of the markup.
    • A studio that passed p-* on a panel still wins.
    • A studio that wrote CSS against the old utility classes should switch to the properties.

Verified

  • test (core 603, every package passes), lint, typecheck, verify:template (0.14.0 tarballs), docs:build, api.md current.
  • e2e: 328 of 328 against a fresh build, 6 of them new.
  • Each new check was negative-tested by breaking what it covers, and each break turned its check red:
    • no cap;
    • the cap a studio could write with an attribute selector (it caps below the breakpoint too);
    • panel padding back as utility classes;
    • <main> without its class;
    • the 640px step lost.

Not checked: OBS's own browser, or the studio itself, which still carries its workarounds.

After merging

Wait for CI on main, then:

git fetch origin
git log --oneline -1 origin/main        # should say "Cut 0.14.0 …"
git tag v0.14.0 origin/main
git push origin v0.14.0

https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU


Generated by Claude Code

claude added 20 commits October 21, 2026 15:00
.github/FUNDING.yml names FourCourtJester on GitHub Sponsors and
fourcourtjester on Ko-fi. GitHub reads it from the default branch only, so
the button appears once this is on main and Sponsorships is ticked under
Settings > General > Features.

Checked: prettier and lint pass. Not checked: either link resolves -- this
environment cannot reach ko-fi.com or github.com/sponsors -- and the button
itself, which only shows once this is merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
…e it

templates/studio/.github/FUNDING.yml and templates/plugin/.github/FUNDING.yml
are the same file as the repository's own. They have to live here rather than
be pushed to the mirrors: a sync deletes everything in a mirror but .git and
copies the template across, so a file added there directly is gone at the
next one. The demo is left without, as asked.

A project started from a template inherits the file, and with it a Sponsor
button pointing at Single Studio. Each README now says to delete it or point
it at your own accounts: the studio's beside its note about LICENSE, the
plugin's as step 4 of building your own.

Checked: lint passes; verify:template builds both templates and the demo
against packed packages. Not checked: the mirrors themselves, which sync on
merge; the links, which this environment cannot reach. Prettier flags
templates/plugin/README.md, as it did before this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
The mirror job failed for both templates on #65, the first push that ever
reached its install step: setup-node@v5 "Unable to locate executable file:
pnpm". v5 turns on a package-manager cache by itself whenever the checked-out
package.json has a "packageManager", and the root one names pnpm, which this
job never installs -- it installs the mirror's copy with npm, from npm. Its
only two runs before were the merge that added it, where nothing changed,
and #63, which changed packages/ and so was left to the release. So the step
had never run.

package-manager-cache: false is setup-node's own switch for that (checked in
its v5 action.yml: "Set to false to disable automatic caching based on the
package manager field in package.json"). Nothing was pushed by the failed
run, so the template repositories are still on their v0.12.0 sync.

Checked: prettier and lint pass. Not checked: the job itself, which can only
run on GitHub. This change touches only .github, so merging it does not
mirror anything; Run workflow on the Mirror workflow, on main, is what will
carry #65's FUNDING.yml across and prove the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
docs/internal/next.md gets a "Raise these first" section, for what the owner
wants reminding of at the top of any "where are we at":

- a right-aligned toolbar on a Panel's title line, for extra components,
  with the three questions to settle before building it;
- Sequence in a real OBS, which 0.12.0 shipped without.

AGENTS.md is what every session reads first, so it now says to start from
next.md, and that section, when asked where things stand. Without that the
note would only be found by somebody already looking for it.

Checked: lint passes. Prettier flags next.md, as it did before this change
(two *emphasis* spans and a table, all older than this section).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
A time input's clock icon is Chrome's to draw, and it draws it dark unless the
page declares a dark theme, so on the board it all but disappeared. A studio
fixed it with color-scheme: dark on its board in its own index.css, and found
it also darkens the time picker's popup, the menus behind Select and the
scrollbars. Every board is dark, so the line belongs in core's base.css,
scoped to .ss-control as the board's cursors are, where it cannot reach a
graphic. Queued, not done.

The toolbar entry is renamed to the owner's word for it: an actions slot.

Checked: lint passes. Not checked: anything in a browser; this only records
the finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
…at built one

SS-Mercy-Riftbound-2v2 built a collapsible Panel with header actions in its
own src/control, because core's Panel has nothing beside its title. Its
notes go into next.md so core can take it over rather than rediscover it:

- the behaviour: everything starts folded on every load, and the fold is
  component state only -- not the show, which would fold every operator's
  board, and not localStorage, so a reload is always the same board;
- the markup: the button inside the h2, not the other way round;
- what bit: .ss-panel-body's display: flex beats the hidden attribute, and a
  grid of panels needs items-start;
- the API it asks for: collapsible, defaultOpen, exported useCollapsed and
  Chevron, an actions slot, and size="sm" on the four header buttons, none
  of which has a size today.

It replaces the earlier actions-slot entry in "Raise these first", which
now points here.

Checked: lint passes. Not checked: the hidden-attribute finding, which is
the studio's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
Two things a studio (SS-Mercy-Riftbound-2v2) built for itself because core's
Panel had nothing beside its title:

- `actions`: anything at all, right-aligned on the title line, or a function
  given `{ open }` that returns it. They stay in the header and work while the
  panel is folded, and in a slim dock take a line of their own under the
  title rather than squeezing it.
- `collapsible`: the title becomes a button that folds the body away. A
  collapsible panel starts folded on every load; `defaultOpen` starts one
  open. The fold is component state only -- not the show, which would fold
  every operator's board, and not storage, so a reload is the same board.
  The body is hidden, not unmounted, so a draft in a folded panel survives.

Off unless asked for, so every existing panel renders as before, except that
its title now sits in a `.ss-panel-header` line. With no title, actions keep
their line and a collapsible panel gets a chevron of its own, named "Fold
panel" / "Open panel"; with nothing for a header to hold there is none. The
button sits inside the h2, never the reverse. `chevron` joins Icon.

Cross-checked against the studio's own Panel and collapse.jsx, and took its
header element and its larger title hit area. Not taken: its Tailwind
`hidden` class on the body. Its notes say `.ss-panel-body`'s display: flex
beats the hidden attribute, but Tailwind's preflight, which every studio
imports, has `[hidden] { display: none !important }`: a `[hidden]` rule
written into base.css for it changed nothing (the folded body measured 0px
tall without it), so it was taken out again rather than kept as code
nothing can catch.

Thirteen e2e checks, in their own section near the end. Placed after Rows
first, they wedged the asset library's folder upload a hundred lines later --
the stall AGENTS.md describes -- and moved, everything passed. Negative-tested
with nine breaks, each turning its check red: body not hidden; body unmounted
while folded; function actions not given the fold; a header with nothing in it;
collapsible by default; defaultOpen ignored; a header that does not wrap; a
header that toggles on any press inside it; a fold remembered in localStorage.

Docs: getting-started's Panel row and a paragraph with an example,
CHANGELOG Unreleased, api.md regenerated, and next.md now says what is built
and what the studio still wants: size="sm" on header buttons, useCollapsed and
Chevron exported for folding cards, and zebra striping.

Checked: test (core 603, every package passes), lint, typecheck,
verify:template, docs:build, and e2e 306 of 306 against a fresh build. Not
checked: in OBS's own browser, or in the studio itself, which still uses its
own Panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
… buttons

Four things from SS-Mercy-Riftbound-2v2 that every studio should have
without writing them:

- Striped panels, by default. Every other panel is a shade lighter, so a long
  board reads as blocks rather than one slab. The studio's own rule and colours:
  rgb(15 23 42 / 0.4) and rgb(30 41 59 / 0.7). The background moved from a
  class on the element into base.css, because the count -- nth-of-type among
  sibling panels -- can only be made in CSS; in the components layer, so a bg-*
  on one panel or a studio's own rule still wins. A panel wrapped in a <div> of
  its own never counts as even; documented rather than worked around.
- color-scheme: dark on .ss-control. Chrome draws a time input's clock icon,
  its picker, a select's list and scrollbars in the page's scheme, and on the
  board they were black on slate. On the board only; a graphic stays normal.
- size="sm" on Confirm, ResetButton, SwapButton and Toggle, for a button in a
  panel's header: text-xs, smaller padding, off the Row's input line, and a
  min-h-6 floor so it is never under the 24px pointer target WCAG 2.5.8 asks
  for. Replaces the studio's `min-h-0! px-2.5! py-1! text-xs!`, which had no
  floor. Measured: 24 or 26px against 40px on a row, 12px text.
- useCollapsed and Chevron, exported from @single-studio/core/control, so a
  card folds the way a Panel does. Panel is now built on them, and both are in
  the API reference's list.

Seven e2e checks, all near the end, and the small buttons mount only when
those checks ask: mounted from the start, they gave earlier checks that find
the board's one swap by class three to choose from. Negative-tested, each
check red when broken for it: stripes removed; the scheme normal; the scheme
on :root, reaching graphics; Toggle ignoring size; a confirming Reset not
passing size on; the small size under 24px; useCollapsed starting open; the
chevron never turning. Removing the min-h-6 floor at first changed nothing,
because the padding alone makes 24px, so the fixture now squeezes one button's
padding to zero and the floor is what holds it at 24.

Checked: test (core 603, every package passes), lint, typecheck,
verify:template, docs:build, and e2e 313 of 313 against a fresh build. The
useCollapsed doc comment was reworded after that run; nothing else changed.
Not checked: OBS's own browser, or the studio itself, which still uses its own
Panel and CSS until core is released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
…nels

A stepper's number field stopped at 4rem, so a stepper was 160px in any cell
and the rest of the cell sat empty -- the negative space at every breakpoint.
Measured on the fixture: 170px of 330 in a 380px dock, 414px of 574 at 640px,
601px of 761 at 1600px. A stopwatch's readout did the same, up to 295px. Both
now grow between their buttons (`grow`, not `flex-1`, so `w-16` is still the
basis and a `col-auto` stepper keeps its own 160px). The same thing
SS-Mercy-Riftbound-2v2's FillStepper did for itself, now for everyone.

The demo, built against these packages: its two score steppers left 121-201px
empty below 1024px on 0.12.0 and leave nothing now, measured at 380, 640, 800,
1024, 1280 and 1600px. Its panels are collapsible: the scoreboard starts open,
the rest folded. Its Swap sides moved into the scoreboard's header at
size="sm", where it works with the panel folded. The demo uses props 0.12.0
does not have, so it has to go out with the release; the mirror leaves a push
that also changes packages/ to the release anyway.

Nine e2e checks at 380, 800 and 1280px: every stepper on a Row reaches its
cell's edge, so does a stopwatch, and a col-auto stepper is 160px. Negative-
tested: the field not growing, the readout not growing, and the field sized
to its cell (which loses col-auto its width) each turned their checks red.

Checked: test (core 603, every package passes), lint, typecheck,
verify:template, docs:build, and e2e 322 of 322 against a fresh build. Not
checked: OBS's own browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
The API reference takes each component's opening paragraph, and the fill
change only touched the markup and its comments, so docs/api.md still
described a stepper as if it sat at 160px. One sentence in each opening
paragraph, and api.md regenerated from the built types.

Checked: lint, typecheck, test (core 603, every package passes), and
`api-reference.mjs --check` says api.md matches the shipped types. Not run:
e2e, since only doc comments changed; it passed, 322 of 322, on a3d3e31.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
… out

Every published package moves to 0.13.0 together, as the release workflow
requires: core, provider-supabase and the four plugins. The plugins' peer
range on core, both templates and the demo move to ^0.13.0 -- in 0.x a caret
on 0.12.0 does not reach 0.13.0, and the demo now uses collapsible,
defaultOpen and size="sm", which 0.12.0 does not have.

A minor: a board looks different after upgrading without asking -- every
other panel lighter, a dark scheme for native pickers, steppers and
stopwatches that fill their cell -- though no prop changes meaning and
nothing is removed.

The CHANGELOG's Unreleased section becomes 0.13.0, with a paragraph saying
why it is a minor. next.md's first reminder becomes moving
SS-Mercy-Riftbound-2v2 onto it.

Checked: `pnpm install --frozen-lockfile` needs no lockfile change. test
(core 603, every package passes), lint and typecheck pass. verify:template
packs the 0.13.0 tarballs and builds both templates and the demo against
them. docs:build passes. Not run: the end-to-end suite, since nothing the
fixture loads changed but version numbers; it passed, 322 of 322, on #67's
code. Not checked: the publish itself, which happens on the tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
…, --ss-* docs

A studio on 0.13.0 filed three findings, each measured on a live board;
next.md now carries them with the measurements, each checked against the
code here:

- The board steps its padding at 640px through utilities in the markup:
  Panel's `p-3 sm:p-4`, and in pages/Control.jsx the <main>'s
  `gap-3 p-3 sm:gap-4 sm:p-4` and the bar's `px-3 py-2.5 sm:px-4 sm:py-3`.
  The <main> has no class of its own. The ask is custom properties with
  today's values as defaults, and an `ss-board` class.
- `col-auto` is `flex: 0 0 auto` with no ceiling, and an ImagePicker is a
  <select> as wide as the longest library key: 136px for `x`, 365px for a
  descriptive logo name, leaving the Field beside it 25px. The ask is a cap
  inside the utility, which a studio cannot write for itself.
- Docs only: read an --ss-* property with a fallback, and how to tell a
  half-applied dev-server stylesheet from broken code.

Raised first, alongside the Mercy move and Sequence in OBS. Prettier also
reformatted three older spots in the file it was already flagging.

Checked: lint passes. Not checked: the studio's measurements, which are its
own; only the code each one points at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
…pickers

Three findings from a studio running 0.13.0 on client work, each measured on
a live board there and checked against the code here.

The board stepped its padding at 640px through utilities in the markup --
Panel's `p-3 sm:p-4`, the <main>'s `gap-3 p-3 sm:gap-4 sm:p-4`, the bar's
`px-3 py-2.5 sm:px-4 sm:py-3` -- which a studio could only beat with CSS
aimed at those classes, and the <main> had no class at all. They are now
`--ss-panel-pad`, `--ss-board-pad`, `--ss-board-gap`, `--ss-bar-pad-x` and
`--ss-bar-pad-y` on `.ss-control`, with the same values and the same step at
40rem by default (measured: 12/12/12/12/10px at 380px, 16/16/16/16/12px at
1280px, as before), and the <main> is `.ss-board`. A studio that sets them on
`.ss-control` gets them at every width: 20px at 380px and at 1280px.

`col-auto` was `flex: 0 0 auto` with no ceiling, and an ImagePicker is a
<select> as wide as the longest library key: the studio measured 365px and a
25px Field beside it for a logo named northwind-esports-primary-logo-2026.
The utility now caps a col-auto picker at `--ss-picker-max` (9.25rem), inside
the utility so `sm:col-auto` caps only from `sm` up. With that name in the
library here: 148px and the Field 798px of a 958px row at 1024px wide, and
330 of 330px at 380px, where the picker has its line to itself.

Docs: getting-started gets the spacing properties, reading an --ss-* property
with a fallback, and a Troubleshooting entry for a dev server serving half a
stylesheet. releasing.md says renaming an --ss-* property is quietly
breaking.

Version: all six packages to 0.14.0; the plugins' peer range, both templates
and the demo to ^0.14.0. A minor: a col-auto picker can render narrower, and
the board's spacing moved out of the markup.

Six e2e checks, negative-tested, each red when broken for it: no cap; the cap
a studio could write by attribute selector (it caps below the breakpoint
too); panel padding back as utilities (a studio's setting then loses);
<main> without its class; the 640px step lost. Two fixes to the checks on
the way: the cap measured at 700px, where a Row still stacks, proved
nothing, so it is measured at 1024px with the field's width exact; and a
missing .ss-board made the spacing measure throw rather than fail, so it now
falls back and fails by name. The fixture's new picker took a panel of its
own, because its library dialog brings an h2 into the panel it sits in.

Checked: test (core 603, every package passes), lint, typecheck,
verify:template (0.14.0 tarballs), docs:build, api.md current, and e2e 328 of
328 against a fresh build. Not checked: OBS's own browser; the studio
itself, which still carries its overrides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
…-limit it

On 2026-10-23 the "Install Chromium" step hung three times -- 7 to 10
minutes each against 20-27 seconds healthy -- and each held a pull request
until somebody noticed and cancelled; a re-run on another runner always went
straight through. The job had no timeout, so GitHub would have allowed 360
minutes.

- The browser is cached (actions/cache@v5, the lowest major on Node 24, as
  every action here is), keyed on Playwright's version, so most runs do not
  download it at all. On a hit only `install-deps` runs, for the system
  packages, which are not in the cache.
- Each install attempt gets two minutes and three tries. `timeout` signals
  the whole process group, so a hung apt-get under sudo dies with it rather
  than holding the dpkg lock for the retry.
- `verify` has a 20-minute limit (healthy: about 4), and the Pages build and
  deploy 10 each (healthy: 19-28s and 9-13s). release.yml and mirror.yml
  already had theirs.

Checked: the retry loop, against a fake installer at 3 seconds instead of
120: one that hangs twice then succeeds passes on attempt 3; one that always
hangs fails after three, with each attempt's child process killed (state Z,
not left running). Both workflow files parse as YAML and pass prettier. Not
checked: any of it on GitHub, which only the run on this push can show --
including that the first run saves the cache and the next restores it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFGhv32jx1vxD7qxaMoFxU
@FourCourtJester
FourCourtJester merged commit 88cc3b6 into main Oct 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants