Angular: Read the story shapes that supply their own markup - #35797
Draft
valentinpalkovic wants to merge 8 commits into
Draft
Angular: Read the story shapes that supply their own markup#35797valentinpalkovic wants to merge 8 commits into
valentinpalkovic wants to merge 8 commits into
Conversation
Contributor
|
This was referenced Aug 7, 2026
valentinpalkovic
commented
Aug 7, 2026
valentinpalkovic
commented
Aug 7, 2026
valentinpalkovic
commented
Aug 7, 2026
valentinpalkovic
force-pushed
the
valentin/angular-story-docs-1-template-snippets
branch
from
August 7, 2026 16:43
7901b02 to
79d7bff
Compare
valentinpalkovic
force-pushed
the
valentin/angular-story-docs-2-story-shapes
branch
from
August 7, 2026 16:43
056d892 to
a431b81
Compare
valentinpalkovic
force-pushed
the
valentin/angular-story-docs-1-template-snippets
branch
from
August 7, 2026 16:49
79d7bff to
3341953
Compare
valentinpalkovic
force-pushed
the
valentin/angular-story-docs-2-story-shapes
branch
from
August 7, 2026 16:49
a431b81 to
23fdf78
Compare
9 tasks
The snippet generator built an element from the component's selector and the
story's args. That is right for a plain `{ args }` story and wrong for every
story that supplies markup itself, which was previously skipped outright.
A story's own `template`, the `{ template }` an inline `render` returns, and the
CSF2 function form are now read and shown as written. A name declared in the
same file is followed to what it holds; an imported one cannot be, so it falls
back to the generated bindings rather than printing the name as markup.
`argsToTemplate(args)` expands to exactly the bindings this generator emits, so
a template built around it is read in full and the wrapper markup survives.
valentinpalkovic
force-pushed
the
valentin/angular-story-docs-2-story-shapes
branch
from
August 8, 2026 05:27
740865a to
e46f5c9
Compare
valentinpalkovic
changed the base branch from
valentin/angular-story-docs-1-template-snippets
to
valentin/angular-story-docs-snippets
August 8, 2026 05:27
9 tasks
9 tasks
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.
Note
Stacked on #35807, which generates Angular story snippets on the server. That one builds the element from the component's metadata and the story's args; this one stops doing that for stories that already wrote their own markup.
What I did
The generator builds an element from the component's selector and the story's args. That is right for a plain
{ args: {...} }story and wrong for every story that supplies markup itself: either the markup the user wrote is replaced by a fabricated element, or the story is skipped and shows no snippet at all.Both columns below are real
buildStoryDocsPayloadoutput over the same story file, on the base branch and on this one. The component issb-buttonwith alabelandcountinput and aclickedoutput, under a meta carryingargs: { label: 'meta' }.{ template: '<sb-button emphasis>hi</sb-button>' }<sb-button [label]="'meta'" (clicked)="clicked($event)"></sb-button><sb-button emphasis>hi</sb-button>{ render: () => ({ template: '<sb-button rendered></sb-button>' }) }<sb-button rendered></sb-button>() => ({ template: '<sb-button csf2></sb-button>' })<sb-button [label]="'meta'" (clicked)="clicked($event)"></sb-button><sb-button csf2></sb-button>const S = { template: '…' }; export { S }<sb-button [label]="'meta'" (clicked)="clicked($event)"></sb-button><sb-button reexported></sb-button>The whole change is one predicate at the end of snippet generation. Markup that was read stands as written; everything else keeps the generated bindings:
Only the markup reading is new
Two of these shapes look like they need a bespoke args reader and do not.
CsfFilealready records both: theexport { X }specifier branch fills_storyAnnotationsfrom the resolved initializer, and theExpressionStatementbranch foldsX.args = {...}into the same record. Running the fixture against the base branch shows the args are already correct there, with no change from this PR:So this PR adds the markup reading and tests that pin the args behaviour so it cannot regress. Following a name to its declaration is a babel scope lookup rather than a resolver of its own.
Following names to what they hold
A
templateorrenderwritten as a name is markup the story really did write. Refusing to look through the name replaced it with a fabricated element, so a bare identifier is now followed back to its declaration in the same file:An imported name binds to an
ImportSpecifier, which has no initializer, so it falls through and stays an identifier. That is the distinction that matters: a story whose markup exists but is unreadable must not silently inherit the meta's markup, because that would print a snippet for code the story never runs. It falls back to the generated bindings instead.argsToTemplateis expanded, not discardedargsToTemplate(args)is the idiom every Angular docs example uses for a custom render, and it lives inside a template literal, which the generator rejected outright: the wrapper markup was thrown away and the story got no snippet.It expands to exactly the bindings this generator already produces, so the template is now read in full:
Three decisions worth a look:
[label]="'Save'", not[label]="label"). That drops the snippet's dependency on the story'sprops: args, so it stands alone.include/excludeare honoured when written as array literals.docs/_snippets/page-story-slots.mddoes:The binding list moved out of the element renderer into a shared
bindingAttributes, because the plain-element path and theargsToTemplateexpansion have to produce byte-identical output.What still can't be read
${…}that is notargsToTemplateand not an arg holding a string, number or booleanBoth fall back to generated bindings. The next slice reports them through
warningso the fallback is not silent.The 15 recorded
server-snippet-*.snapshotfiles are unchanged by this PR: none of the harness fixtures declares atemplateor arender, so none of them takes the new path.Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
features.experimentalDocgenServeron.argsToTemplateidiom:<div class="wrap">wrapper and fills in the bindings.constin the same file and reference it by name. The snippet still shows the markup.constinto another file and import it. The snippet falls back to generated bindings rather than printing the variable name.Documentation
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsDeclare whether manual QA will be needed for this PR during the next release, through
qa:neededorqa:skipMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.