Skip to content

Storybook sidebar: scattered component groups, and a guideline rule that only held by accident - #1256

Merged
bert-e merged 6 commits into
development/1.0from
improvement/storybook-guideline-first
Oct 15, 2026
Merged

bert-e merged 6 commits into
development/1.0from
improvement/storybook-guideline-first

Conversation

@JeanMarcMilletScality

@JeanMarcMilletScality JeanMarcMilletScality commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR — Feedback and Progress & loading were not grouped with the other component categories in the sidebar; their components were scattered through the loose list, below Styling. They are back in place, and "a component's guideline comes first" is now a rule the sort guarantees and a test enforces, instead of something filenames happened to produce.

📷 Screenshots

The same tree at the same viewport, with Style, Guidelines and Templates collapsed and Icon opened. On the left, the sort before this PR: Styling sits straight after Inputs, Feedback has fallen between InlineInput and AttachmentTable, Progress & loading between ButtonGroup and ConstrainedText, and Icon's attached page is called Icon and comes before Stories only because of where its file sits.

Storybook sidebar before and after the ordering change

Context / Why

Two separate things were wrong with storySort.order, and neither announced itself.

The categories. Feedback and Progress & loading were written as array elements rather than as a string followed by its children:

'Inputs',
[ ['Checkbox', ['Guideline', '*']], … ],   // children of Inputs — correct
['Feedback', [['Modal', ['Guideline', '*']]]],        // ← an element, not a string
['Progress & loading', [['Stepper', ['Guideline', '*']]]],
'Styling',
'Deprecated',

Storybook ranks a name with order.indexOf(name), so indexOf('Feedback') was -1: the category was unranked and fell to order.length, i.e. after Styling and Deprecated and tied with every component that has no category at all. The sidebar read Navigation, Data Display, Inputs, Styling, Icon, AttachmentTable, Feedback, ButtonGroup, Progress & loading, … — the two groups scattered across five positions.

The per-component sub-lists. ['Checkbox', ['Guideline', '*']] and its five siblings never ran. Storybook's comparator opens with if (a.title === b.title && !options.includeNames) return 0, and includeNames was never set — a guideline page and its stories share a title, so the pair returned 0 before the list was ever consulted. Guideline-first held anyway for 16 of the 17 guidelines, because <name>.guideline.mdx sorts before <name>.stories.tsx in the same directory and the sort is stable. So the configuration carried six lines that did nothing, and the rule they were meant to express was being supplied by filenames.

🧩 Approach

storySort also accepts a comparator, which states each rule once:

['Introduction', 'Component index', 'Style', 'Guidelines', 'Templates', 'Components']
['Navigation', 'Data Display', 'Inputs', 'Feedback', 'Progress & loading', 'Styling', 'Deprecated']
['Guideline', 'Stories']        // ← the rule that used to need one line per component

The comparator is a total order: two entries that differ and are both unranked fall back to their names rather than to 0. That is what makes the guideline rule hold rather than depend on where a file sits — Array.prototype.sort cannot move an entry across a run it has been told is equal, so a 0 there strands a page whose file is not beside its component's stories. Entries sharing a title are consequently always contiguous, and within a component the stories keep the order they were authored in.

Two things had to change for the rule to be true everywhere rather than nearly:

  • stories/Icon.mdx followed CONTRIBUTING.md's recipe literally — <Meta of={IconStories} />, no name — so it was never a Guideline page at all. It now declares one, and the recipe does too. Without the name a page is named after docs.defaultName, takes the generated props table's place, and the component quietly loses a page instead of gaining one. The test that pins this first read the <Meta> tag as one string anchored on of=, which skipped every page writing name first — and the repo writes it both ways, so 11 pages were being checked where 18 exist. It now matches the attributes independently.
  • Components/Data Display/Charts/GlobalHealthBar is the one guideline that genuinely came last, its file being misspelled globalheathbarrecharts.guideline.mdx. It no longer matters where the filename sorts.

Visible side effect: components with no category are now alphabetical within their group — Accordion, AttachmentTable, Button, ButtonGroup, … — where they previously followed the glob's order, Accordion, Button, Icon, IconHelp, InfoMessage, InlineInput, AttachmentTable, ….

🔍 Review focus

  • 🔴 .storybook/preview.tsx › storySort — two Storybook constraints shape how this is written, and breaking either takes the whole sidebar down rather than degrading it. The property is parsed statically, so a reference to a function defined above is rejected at index-build time with "should be defined inline"; and the extracted source is then evaluated as plain JavaScript, so TypeScript parameter annotations inside it fail with SyntaxError: Unexpected token ':'. Both surface as /index.json returning 500 and an empty sidebar, never as a compile error — tsc --noEmit passes in both broken states.
  • 🟡 .storybook/preview.test.ts — the suite reads the repository rather than mocking it, in two places, and that is the point rather than an oversight. readSortSidebar pulls the arrow's source out of the file and evaluates it the way Storybook does, so the suite fails on either constraint above instead of passing against an import Storybook never performs; importing the module would also need an identity-obj-proxy that jest.config.js names but the project does not install. The parameterised case walks stories/ because the invariant it enforces is about the files themselves — a fixture would assert the fixture. Neither read touches a clock, the network or anything non-deterministic: both see versioned source in the working tree.

🧪 How to test

  1. npm test — .storybook/preview.test.ts covers the ordering rules, spells out all six section names and all seven category names so a misspelling cannot pass, and asserts that every MDX page attached with of={…} declares name="Guideline". It also prints, without failing, the components that have no guideline yet.
  2. npm run storybook. Under Components, the group order is now Navigation, Data Display, Inputs, Feedback, Progress & loading, Styling, followed by the uncategorised components in alphabetical order. Deprecated ranks last but currently holds no story, so it does not appear; only the test pins its place. Before this change, Feedback and Progress & loading sat after Styling and Deprecated and were interleaved with the loose entries — compare Components/Styling/Box against Components/Feedback/Banner, whose order is reversed by this PR.
  3. Open Components / Data Display / Charts / GlobalHealthBar and Components / Icon: Guideline now comes first in both. It was last in the first and absent from the second.
  4. Confirm the top-level order is untouched: Introduction, Component index, Style, Guidelines, Templates, Components.

Follow-up

  • globalheathbarrecharts.guideline.mdx is misspelled and does not match its stories file. The sort no longer depends on it, but the filename is worth fixing on its own.
  • The generated props page is named Stories (docs.defaultName in main.ts), which reads oddly now that it sits between the guideline and the actual stories. Renaming it touches every component at once and is deliberately not in this PR.
  • Most components have no guideline page at all. npm test now prints the list rather than asserting on it — failing the suite would punish work nobody has been asked to do — so the backlog is visible to whoever next touches one of those files. Writing them is a separate piece of work.

…s, for every component

storySort.order could only do this by naming each component, so the six listed
got it and everyone else — Accordion, and anything added since — landed its
guideline after its stories, with nothing to signal the omission. A comparator
ranks the sections and categories as before, then puts a component's guideline
first whatever the component.
@bert-e

bert-e commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Hello jeanmarcmilletscality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval ⭐
/bypass_build_status Bypass the build and test status ⭐
/bypass_commit_size Bypass the check on the size of the changeset TBA ⭐
/bypass_incompatible_branch Bypass the check on the source branch prefix ⭐
/bypass_jira_check Bypass the Jira issue check ⭐
/bypass_peer_approval Bypass the pull request peers' approval ⭐
/bypass_leader_approval Bypass the pull request leaders' approval ⭐
/bypass_source_branch_lineage Bypass the cross-branch contamination check ⭐
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request.
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e

bert-e commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

Comment thread .storybook/preview.tsx Outdated
: depth === 1 && left[0] === 'Components'
? CATEGORIES
: [];
return rankIn(order, left[depth] ?? '') - rankIn(order, right[depth] ?? '');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparator is not transitive. Two different components tie (0), but two pages of the same component do not. For example, Checkbox/Default ties with Toggle/Guideline, and Toggle/Guideline ties with Checkbox/Guideline, yet Checkbox/Guideline < Checkbox/Default. With an inconsistent comparator, the spec leaves the sort order implementation-defined.

This matters here because the guideline docs entries are indexed after all CSF entries. V8's TimSort places them with binary insertion and galloping, so a guideline can land after its own stories when the probe hits another component's entry. Accordion working in a manual check does not prove other components work.

Break ties between unranked siblings by name so the order is total. Paths are stories/<Name>/, so this stays close to the current load order:

Suggested change
return rankIn(order, left[depth] ?? '') - rankIn(order, right[depth] ?? '');
return (
rankIn(order, left[depth] ?? '') - rankIn(order, right[depth] ?? '') ||
(left[depth] ?? '').localeCompare(right[depth] ?? '')
);

@bert-e

bert-e commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

@JeanMarcMilletScality JeanMarcMilletScality changed the title Storybook sidebar: a component guideline now comes before its stories, without naming the component Storybook sidebar: the Feedback and Progress & loading groups were scattered through the component list Oct 12, 2026
…relying on filenames

The comparator returned 0 for any two entries with different titles, so the sort could not move a guideline past a run it had been told was equal: a page whose file does not sit beside its component's stories stayed last, with nothing to signal it. Comparing unranked names makes the order total, which also groups a component's pages together wherever their files live.

stories/Icon.mdx followed the contributing recipe literally and so declared no name, which made it an unnamed attached page rather than a guideline. The recipe now carries the name, and a test fails on any attached page that omits it.
@eve-scality

eve-scality commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

🔴 Request changes

Why the tests need changes

  • The test depends on real time, randomness, network or filesystem

Findings

  • ⚠️ Add a test for slash-with-spaces titles to guard the normalisation logic

  • ⚠️ No test covers two uncategorised components sorting alphabetically between themselves

This review is advisory and does not block the merge.

Full review

The test depends on real time, randomness, network or filesystem

.storybook/preview.test.ts:8
const previewSource = fs.readFileSync(previewPath, 'utf8');
.storybook/preview.test.ts:130
fs.readdirSync(dir, { withFileTypes: true }).flatMap((item) => {
.storybook/preview.test.ts:137
const source = fs.readFileSync(file, 'utf8');
Inject the clock, randomness or I/O, or explain why the dependency is safe here.

Findings

  • ⚠️ Add a test for slash-with-spaces titles to guard the normalisation logic
    .storybook/preview.test.ts:82
    The comparator normalises titles with .trim().split(/\s*\/\s*/) (preview.tsx lines 114–115), so 'Components / Navigation / Tabs' and 'Components/Navigation/Tabs' are treated identically. No test exercises a title written with spaces around the slash. If the normalisation were accidentally removed, a story file using that style would fall outside all ranked groups and drop to the end of the sidebar — and no test would catch it. The 'orders the sections and every component category' test is the natural place to add one such entry.
  • ⚠️ No test covers two uncategorised components sorting alphabetically between themselves
    .storybook/preview.test.ts:66
    When two entries are both unranked at the same title depth, the comparator returns left[depth].localeCompare(right[depth]) rather than 0 (preview.tsx line 130). The PR description calls this an intentional change from the previous glob order. No test exercises a pair of entries with no category and asserts they appear alphabetically. Every depth-1 comparison in the current test suite involves a ranked category, so replacing the localeCompare call with a return of 0 would leave all existing tests green.

@JeanMarcMilletScality JeanMarcMilletScality changed the title Storybook sidebar: the Feedback and Progress & loading groups were scattered through the component list Storybook sidebar: scattered component groups, and a guideline rule that only held by accident Oct 12, 2026
The test exercised three of the seven names, and not 'Progress & loading'. A misspelling there goes unranked rather than erroring, which drops the category below the ones that still rank — the defect this sort was written to fix, and every assertion would have stayed green.
The attached-page check matched the Meta tag as one string anchored on `of=`, so it skipped every page writing `name` first — 11 pages were checked where 18 exist. The tag is now read as a whole.

The new case prints rather than asserts: most story files predate the rule, and failing the suite for them would punish work nobody has been asked to do.
@JeanMarcMilletScality
JeanMarcMilletScality marked this pull request as ready for review October 12, 2026 13:16
…eakage

The missing-guideline scan ran inside an it() with no expect, so it could not
fail whatever the traversal produced, while occupying a slot a reader expects
to enforce something. It now runs at module level and still prints the backlog.

The comparator was sliced out of preview.tsx by matching braces from the first
one after `storySort:`, which a brace inside a string or a comment would have
broken. Babel locates the node instead, and the two constraints that take the
whole sidebar down — the arrow must stay inline, and its parameters cannot be
annotated — are now checked there, each with a message saying what to fix.
@bert-e

bert-e commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

@JeanMarcMilletScality

Copy link
Copy Markdown
Contributor Author

/approve

@bert-e

bert-e commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Queue out of order

The changeset has received all authorizations to enter the merge queue,
however the merge queue is currently in an incoherent state. In order
to protect this pull request, I have not added the changeset to the
queue.

Look into https://devdocs.scality.net/support/faq/#bert-e for guidance about
what to do next to unlock the situation, or ask for help on the #ci-support
Slack channel.

The following options are set: approve

@JeanMarcMilletScality

Copy link
Copy Markdown
Contributor Author

/status

@bert-e

bert-e commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Status

check status
➡️ Approvals ☀️

The following options are set: approve

@bert-e

bert-e commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Queue out of order

The changeset has received all authorizations to enter the merge queue,
however the merge queue is currently in an incoherent state. In order
to protect this pull request, I have not added the changeset to the
queue.

Look into https://devdocs.scality.net/support/faq/#bert-e for guidance about
what to do next to unlock the situation, or ask for help on the #ci-support
Slack channel.

The following options are set: approve

@JeanMarcMilletScality

Copy link
Copy Markdown
Contributor Author

/status

@bert-e

bert-e commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Status

check status
➡️ Approvals ☀️

The following options are set: approve

@bert-e

bert-e commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

In the queue

The changeset has received all authorizations and has been added to the
relevant queue(s). The queue(s) will be merged in the target development
branch(es) as soon as builds have passed.

The changeset will be merged in:

  • ✔️ development/1.0

There is no action required on your side. You will be notified here once
the changeset has been merged. In the unlikely event that the changeset
fails permanently on the queue, a member of the admin team will
contact you to help resolve the matter.

IMPORTANT

Please do not attempt to modify this pull request.

  • Any commit you add on the source branch will trigger a new cycle after the
    current queue is merged.
  • Any commit you add on one of the integration branches will be lost.

If you need this pull request to be removed from the queue, please contact a
member of the admin team now.

The following options are set: approve

@bert-e

bert-e commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

I have successfully merged the changeset of this pull request
into targetted development branches:

  • ✔️ development/1.0

Please check the status of the associated issue None.

Goodbye jeanmarcmilletscality.

@bert-e
bert-e merged commit 4e52bb2 into development/1.0 Oct 15, 2026
9 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.

4 participants