FHL: fluent v9 and headless docsites upgrade - #35
Draft
mainframev wants to merge 52 commits into
Draft
mainframev wants to merge 52 commits into
mainframev wants to merge 52 commits into
Conversation
…0-2 partial) Adds a React Router + Vite + Fumadocs application that renders documentation from the existing Storybook story modules, with no duplication of example code. The site imports story modules directly and reads meta/parameters at runtime, applying the same two build rules Storybook applies: `.md` as a string module, and the existing `@fluentui/babel-preset-storybook-full-source` plugin for standalone per-example source. Both description locations (sibling `.md` files and inline `parameters`) therefore resolve identically in both hosts. Published additively at /docs/react and /docs/headless. No existing address changes and no redirects; the Storybook sites are untouched. - widen the export-to-sandbox addon's public API (`scaffold`, `openCodeSandbox`, `openStackblitz`, `getDependencies`, `Data`) so the export works outside Storybook, and take an injected `targetDocument` instead of the ambient one (repo rule #3) - bump root vite 6.4.2 -> 7.3.6; fumadocs-mdx's generated content index uses the `import.meta.glob` `base` option, which is Vite 7+ only despite its declared peer range. The web-components and chart-web-components Storybooks are verified on Vite 7. - build-time docgen manifest, with slot-shorthand and native-props detection ported as pure functions so the rendered page and machine-readable output cannot disagree - Tailwind preflight scoped away from example previews, and Griffel styles injected during prerender so examples are styled before hydration - record authored example order at build time; ES module namespace objects sort their keys, which had promoted the wrong example to primary - guard that fails the build, naming the module, on unsupported story capabilities - accessibility gate (axe-core via Playwright) over representative pages Planning artifacts for this change live in openspec/changes/migrate-docsite-to-fumadocs.
Adds 53 headless component pages and the one-shot generator that produced them. The generator recurses, so grouped entry points (Concepts/Positioning, Tags/Tag) are picked up rather than skipped, and it never overwrites an existing page. - alias the headless library's subpath exports so Rollup can resolve them - load the headless design tokens in the /docs/headless route only. Storybook imports them globally from its preview; without them the CSS Module rules applied but every token reference fell back, so border radii, gaps and padding were wrong. Accordion and Card now match Storybook on all compared computed properties. - suppress MODULE_LEVEL_DIRECTIVE/SOURCEMAP_ERROR warnings. Fluent sources carry 'use client', which Rollup warns about once per module and, with sourcemaps disabled, cannot locate — producing 608 lines of noise that masked a genuine resolve failure. Every other warning code still surfaces. - return a passthrough sourcemap from the story-order plugin instead of null Build: 58 routes, 14.8s cold (6 routes was 11.8s), so prerender scaling is startup-dominated rather than per-page.
…e rule `app/lib/source.ts` was never committed. The repo-wide .gitignore has a bare `lib` rule for build output, which silently excluded the app's `app/lib/` source directory. The original worktree still built because the file existed locally as an untracked file; a fresh worktree exposed the gap. Moved to `app/source.ts` rather than adding a .gitignore negation, so the file cannot be swallowed again by a rule that is correct for the rest of the monorepo. Also declare the Nx build dependencies the Vite config needs at config-load time (the babel preset and the sandbox addon) plus the component packages the app imports, so the site builds from a clean checkout instead of relying on previously built output.
…ok does Generates Vite aliases from the workspace TypeScript path mappings, the Vite equivalent of the TsconfigPathsPlugin that Storybook already uses. Previously the site resolved Fluent packages through node, so it only built where `lib/` output happened to exist from earlier builds. A clean checkout failed with "Failed to resolve entry for package @fluentui/react-components", and a stale `lib/` could silently win over the source being edited. This also removes the need to hand-maintain one alias per `*-stories` package, which would not have scaled across the remaining trees. Verified from a clean state (no dist, no .source, no docgen output, nx cache reset): `nx run public-docsite-v9-new:build` succeeds in 67s, prerendering 58 routes across 10 dependency tasks. type-check, 23 unit tests and 4 accessibility checks all pass.
Extends the page generator to every component package that ships stories, producing 91 v9 pages. 149 routes now build and prerender clean. Excluded, per the proposal's non-goals: the migration shims (they embed v8/v0 playgrounds that would pull those libraries into the bundle), the headless preview (its own tree), and workbench-internal packages. Two resolution fixes were needed once the full corpus was in play: - alias the three deprecated packages (react-alert, react-infobutton, react-virtualizer). They have no tsconfig path entry, but `@fluentui/react-components/unstable` still re-exports them, so any page importing the unstable barrel failed to resolve. - add `tabster` and `keyborg` to ssr.noExternal. Both are CommonJS; left external, Node's ESM loader rejects their named imports during prerendering. Spot-checked Button, Menu, DataGrid, Dialog, Tree and Field: 200s, examples render (8-28 per page), no error boundaries, no console errors. Known gap recorded as task 4.9: the Fumadocs content index is generated with eager globs, so all examples land in one 10M chunk instead of being split per page.
…eaking sandbox exports
Component pages now read `meta.decorators` from the imported story module and apply them around each example, instead of leaving examples unwrapped. Taking the decorators from the module keeps them on the same source of truth as the examples themselves (design D1). The alternative was re-declaring each layout wrapper on its docs page, which would silently drift from Storybook whenever a decorator changed. Verified against the computed styles: Field constrains to 400px, Avatar renders flex with a 5px gap, Label uses space-evenly. Covers the 10 v9 decorator pages plus the headless SwatchPicker.
Every page previously modulepreloaded a single 10M chunk, because the Fumadocs content
index was generated with eager globs — so opening one component page downloaded the
examples for all 149 pages.
Collections are now async, so only frontmatter stays eager and each page's compiled body
is a separate chunk. Rendering that body suspends, so the server entry uses `prerender`
from react-dom/static, which waits for suspended boundaries instead of emitting fallbacks.
Prerendered HTML therefore still contains full content and Griffel styles, verified with
JavaScript disabled.
largest chunk 10M -> 476K
chunks 22 -> 464
per-page js/css ~10M -> ~0.7M
Enabling this surfaced a content bug that had been present since the first build: sibling
`*Description.md` imports were compiled into MDX components by fumadocs-mdx instead of read
as strings, so component pages rendered `function MDXContent(props = {}) {...}` where their
prose should be. It went unnoticed because the one hand-written page took its visible
description from frontmatter. The raw-string plugin now redirects those imports to a
base64-encoded virtual module id, which the MDX file filter cannot match.
Verified: 149 routes, no hydration errors, examples render, no-JS styling intact,
type-check clean, 23 unit and 4 accessibility tests passing.
… defects Adds a `test-content` target that sweeps all prerendered pages for symptoms rather than relying on spot-checks: compiled-source leaks, error boundaries, missing API data, application-error placeholders and unrendered `[object Object]`. Generated pages make "verify one page, assume the rest" unsafe. The previous commit fixed descriptions that had rendered as JavaScript source across 144 pages; that survived as long as it did because only the one hand-written page was being checked. Running it immediately found a second defect on 13 pages, recorded as task 4.12: slot names leak onto the rendered element as DOM attributes (`icon="[object Object]"` on Avatar, `root=`/`avatar=` on headless AvatarGroup). The slot content still renders correctly as a child, so slots resolve but are not stripped from root props, and it persists after hydration. Needs a Storybook comparison to establish whether the source-aliasing introduced it before anything is changed.
Rendering AvatarGroup in both hosts shows Storybook emits the identical attributes (role, id, root, avatar, class, aria-label), so the slot names appearing as `[object Object]` attributes are upstream Fluent behaviour that this site reproduces faithfully — not a defect introduced by resolving packages to source. The audit now only flags `[object Object]` in page text, where it does indicate an unresolved expression. Flagging the attribute form would leave the gate permanently red across 13 pages, which is how a check stops being read. All 148 prerendered pages now pass the audit.
Adds a one-shot codemod converting Storybook MDX docs to Fumadocs pages, and the 54 pages it produced. The site now builds 203 routes. The codemod strips the `<Meta>` marker and docs-block imports, derives the frontmatter title and file path from the meta title, and removes the leading heading that would otherwise duplicate the frontmatter title as a second h1. Two rewrites make the moved pages work: - relative imports (helper modules, images) become `@repo/*` alias imports, so pages keep importing the original files rather than copies that would drift - Storybook id links (`/docs/a-b-c--docs`) become real paths, using a map built from both conceptual pages and component pages. 71 unresolved targets dropped to 7, and those remaining are external icon docs, deliberately excluded shims, or stale links in the original content. 64 pages are excluded: the per-component v8/v0 migration guides render legacy components and pull `@fluentui/react` and `@fluentui/react-northstar` into the bundle. That happens transitively through story modules, so inspecting page text does not reveal it — it was isolated by bisecting the content tree. Excluded by path for the same reason as the migration shim packages, and still available on the Storybook docsite. All 202 prerendered pages pass the content audit; type-check, 23 unit tests and 4 accessibility checks pass.
…t output Adds a `test-links` target that resolves page links against the routes the site actually serves and asset links against files on disk, checking the built output rather than source. Links are rewritten by a codemod across generated pages, so a mistake there is systematic rather than a typo — and this caught one immediately: the rewritten paths included the router basename, so every migrated page linked to `/docs/docs/react/...`. Content links are resolved against the basename and must not repeat it. Links into the deliberately excluded v8/v0 migration subtree now point at the Storybook docsite, which still hosts that content, instead of dangling. 20,759 links across 202 pages resolve.
…chitecture Pages were flat at the root and in alphabetical order, losing the grouping Storybook presents. Two separate things were missing. Position: a page's path now comes from its story `meta.title`, not its directory, because the title is what carries the hierarchy (`Components/Badge/CounterBadge`, `Compat Components/Calendar`, `Utilities/...`). Deriving paths from directories flattened every component to the root. `Components/Button/Button` collapses to `components/button`, matching how Storybook presents it. Order: `scripts/generate-nav.mjs` transcribes `options.storySort.order` — which exists only as a hand-maintained array in the Storybook preview config and is not derivable from titles or file names — into `meta.json` files, using Fumadocs' `...` rest so unlisted pages still appear rather than disappearing. Also fixes slug generation, which mangled spaces and acronyms: `Compat Components` produced a directory with a space in it, and `APIs` became `ap-is`. Sidebar now reads Concepts, Theme, Components, ... with components nested under their group. 202 routes; content audit and 11,297 internal links pass; accessibility page paths updated to match the new structure.
The site is served as static files, so the `api/search` route from the Fumadocs setup guide cannot exist. The dialog is configured as a static client that fetches a prebuilt index instead. The index is built by reading the content directory directly rather than through the Fumadocs loader, which is built on `import.meta.glob` and only resolves inside Vite — so it cannot be driven from a plain Node build step. Fenced code, JSX and import statements are stripped before indexing, otherwise every component page matches on framework noise rather than its prose. Verified in a browser: 200 pages indexed, searching returns results, and the only search-related network request is for the static index itself. The index is 3.5MB and fetched lazily when the dialog opens. Worth revisiting if that proves heavy in practice.
Adds navigation links to the charts Storybook, the v8/v0 migration guides and the component workbench itself, all marked external. That content is deliberately not migrated (proposal Non-goals), so linking out keeps it reachable rather than simply absent from the site. Layout tabs for switching between the two documentation trees are attempted here but do not render: passing `tabs` to `DocsLayout` type-checks yet produces no markup, most likely because Fumadocs derives tabs from a single page tree rather than accepting arbitrary cross-tree urls, and each tree is its own loader. Left open as task 6.2 — readers can still move between trees via the outbound links.
Generates an llms.txt summary and a plain-text rendering for each of the 201 pages, derived from the same content the site renders rather than from a built artifact. Example source is produced by the same babel plugin that feeds the source panels, so the code published here is byte-identical to what a reader sees on the page. The previous generator could not offer that: it drove a headless browser over a built Storybook and round-tripped MDX through HTML and Turndown, losing components and frontmatter. Prop types come from the shared docgen manifest, so the abbreviated slot forms match the rendered table — Button's `icon` publishes as `Slot<"span">` rather than the expanded `WithSlotShorthandValue<...>` the old extractor emitted. That divergence between the two published surfaces was one of the motivations for the manifest. Two formatting fixes the first output made obvious: `|` in union types is escaped, since it otherwise splits Markdown table rows, and very long generated unions are truncated — `focusgroup` alone expands to several thousand members and buried the rest of the table.
…d have removed Removing it predates the decision that the workbench stays published and deprecated rather than retired, and conflicts with the requirement that it remain functional. Its output is consumed by the workbench's own copy-as-markdown button and lives at a different address from the new site's, so the new generator does not replace it.
…layout tabs Passing `tabs` to DocsLayout on its own rendered nothing. Each tree is served by its own `loader()`, so a tab cannot be bound to a page tree; Fumadocs needs `urls` to resolve the active tab in that case, and `tabMode="top"` to place them. Verified in a browser: both tabs render, clicking switches to /docs/headless, and that tree's sidebar loads.
Adds an `audit:a11y` sweep covering all prerendered pages, grouped by rule. It is a sweep rather than a gate — too slow to run per commit — complementing the three-page chrome check that does gate. It reports 13 serious/critical rules, all inside the rendered examples rather than the docs chrome: missing form labels, unnamed progressbars and buttons, scrollable regions without keyboard access. Provenance was verified rather than assumed. Headless ProgressBar reports `aria-progressbar-name` identically in Storybook and on the new site, so these are pre-existing defects in the examples that the migration surfaces but does not introduce. Recorded as task 7.0 for the component teams.
Adds a README covering the model that matters most — examples are never duplicated, so changing one means changing its story — along with the build gates, the one-shot page generators, the deliberate exclusions, and the handful of non-obvious decisions a maintainer would otherwise have to rediscover (source resolution, async collections, the prerender entry, Tailwind scoping). Also records in AGENTS.md that both sites are published and that examples are edited in their story rather than in the docs site. Confirms no existing published address changed: the deploy workflow still copies the four existing trees unchanged and the new site only adds `_pages/docs`.
…site-wide No page rendered a props table or API disclosure cards. Two causes compounded: the docgen manifest still listed only Button, left over from the Phase 1 walking skeleton, and the page generator never emitted the `docgen` prop — so regenerating content overwrote the one hand-written page that had it. Components are now discovered from each package's `library/src/components/<Name>/<Name>.tsx` rather than a hand-maintained list, which is what went stale. They are parsed one package at a time: handing react-docgen-typescript all 351 component files at once returns entries with zero props, which is how the first attempt at this produced 214 empty components rather than an obvious failure. 214 components documented, 122 pages now carry a props table. The content audit gains a rule for this: a page documenting a component that exists in the manifest must render its table. The existing rule only caught a *failed* lookup, and passing no `docgen` prop rendered nothing at all — leaving no text for an audit to match.
…rsion to the docs Exported sandboxes pinned `@types/react ^17` against a React `^18` runtime, while both documentation hosts render React 19. A reader opening an example got a project that type-checked against a different React than the example was written for. The scaffold's dev dependencies and both hosts' required dependencies now follow the version actually rendered.
Assembled the artifact exactly as the workflow does and served it statically: the new /docs tree renders with examples, props tables, llms.txt and search index, and the existing /headless Storybook still serves alongside it. Only local dev and preview had been exercised before.
…ied by parity check Adds an `audit:parity` check that compares every component Storybook indexes against the examples the new site publishes, reading Storybook's own index.json rather than scraping. Sampling pages by hand cannot answer "did the migration lose anything"; a page can render, pass every other gate, and still be missing examples. It immediately found 10 story entry points under `apps/public-docsite-v9/src` that the generator never scanned — focus-management and theme utilities, positioning concepts, accessibility scenarios. They appear in Storybook's sidebar like any other page and had been silently dropped, because the generator only looked at component packages. Nine are now generated. `useUncontrolledFocus` is excluded because its example renders v8's FocusTrapZone, and entry points whose examples import v8/v0 are now skipped generically rather than by name. Parity now: headless 53/53 with every story present; react 99/123, the 24 absent being the 23 migration shims plus that one page, all excluded by design. 210 routes; content audit and 11,960 internal links pass.
… own preview Migrated MDX pages embedded examples with `FluentCanvas`, the Storybook addon's preview wrapper. That pulled the addon into the documentation bundle and bypassed this site's preview chrome, so those examples had no theming, direction or error boundary. They now use `<StoryPreview>`, and the addon no longer appears in the output at all. `<FluentStory id="…">` references a story by id, so there is no component to hand `<StoryPreview>`; it becomes a link to that example's own page and anchor, as do Storybook's `?path=` deep links. Doing this exposed three heading defects the codemod had left behind: `<h1 class="sbdocs-title">` carried over from Storybook, `<Title>` usage surviving after its import was stripped — undefined at hydration, failing the page with React error microsoft#419 — and mid-document `#` headings producing a second h1. The content audit gains rules for both, scoped to the page's own headings so components that legitimately render an h1 (AccordionHeader, Image's examples, the theme designer) are not flagged. Narrowing that scope took three attempts; each earlier version flagged example markup as a page defect.
Adds a controls panel for stories that declare `argTypes`, merging component-level and story-level definitions the way Storybook does, honouring `table.disable` and `control: false`, and inferring a control from the declared type where none is given. Story `args` defaults now apply whether or not controls are shown. Hand-rolled rather than adopting Fumadocs' `defineStory`, which would mean re-authoring every example — the duplication this whole approach exists to avoid. Object-valued options are omitted: a select can only carry a string, and Avatar's `badge` option rendered as "[object Object]" in the list. Two behaviours are inherited from how stories are written rather than from this panel: a story that spreads props before its own attributes cannot be varied, matching Storybook, and stories declaring `args` without `argTypes` get no controls. Deriving those from the docgen manifest is recorded as 4.4a.
Most stories declare `args` without `argTypes`, so they rendered a fixed example with no way to vary it — CounterBadge showed `count: 5` and nothing else. Storybook infers controls from the same type information the docgen manifest already holds, so it is used when a story declares none. Authored `argTypes` still win, so an explicit choice to expose or hide a control is never overridden. Slots, refs, handlers and unions longer than twelve options are left out; offering every prop produces a panel nobody can use. CounterBadge now has nine working controls. This surfaced a bug that predates it: the primary example's call site never received `decorators`, `metaArgTypes` or `docgen` — only the secondary examples did. The most prominent example on every page had been rendering without its decorator, and would have had no controls either. Both call sites now pass the same props. Two audit rules added: a page whose examples list headings but render none (which is how TimePicker's hydration failure presents), and object-valued select options, which rendered as "[object Object]" on Avatar. TimePicker's page is recorded as task 4.14 — it fails at hydration and shows no examples.
…ts as values Derived controls read defaults from the docgen manifest, where every value is a string and a prop with no default is recorded as the literal "undefined". Passing that through handed the component the *string* "undefined", which is not the same as leaving the prop unset. TimePicker's `hourCycle` then reached `toLocaleTimeString`, which Node's ICU rejects for a string value — the value `undefined` is accepted fine — throwing during prerender and taking the page's content boundary with it. The page listed seven example headings and rendered none of them. It now renders 6 examples and 30 prop rows, prerendered rather than falling back to client rendering. The stack pointed into react-timepicker-compat and a fix there looked obvious. Reproducing the throw first showed `hourCycle: undefined` does not throw at all, which is what located the real cause — otherwise a published component would have been changed to accommodate a bug introduced here.
… see The link checker matched `href="(/docs/[^"#?]*)"`. A character class excluding `#` does not match an href containing one and then stop at it — it fails to match the href at all, so every anchored link was skipped silently. "All internal links resolve" was true only of the links being looked at. Widening the pattern surfaced 21 broken links, and the checker now verifies fragments too, since an anchor pointing at nothing leaves the reader at the top of the page with no indication anything is wrong. The defects behind them: - Component routes were registered as `/docs/<tree>/...` while page routes used `/<tree>/...`. The router adds the basename itself, so those links resolved to `/docs/docs/...`. - A page's path was derived from `meta.title` when generating the page but from the story's directory when generating links to it, so `Components/Accordion` was linked at `/react/accordion` and generated at `/react/components/accordion`. The anchor rule had two further copies. All four now share `scripts/story-route.mjs`. - `nameToHash` lowercased the export name, producing `motioncustom` where Storybook uses `motion-custom`, despite a comment claiming the schemes matched. - 17 links kept Storybook's `?path=` query form and resolved against the current page. - Storybook resolved images with `require()` inside JSX. An ES module has no `require`, so three pages threw during prerender and fell back to client rendering. - `Concepts/Developer/Positioning Components` had no page at all; parity had been reporting 98/123 against a recorded 99/123. It now renders its 16 examples. 12,041 links across 209 pages resolve, paths and fragments alike. The shared path and anchor derivation is covered by tests because the whole site's URLs depend on it.
…e repository Twelve react-card stories reference their images as raw.githubusercontent.com URLs on the default branch rather than importing them. Keeping that is the right call: a sandbox exported from the page cannot resolve a repository-relative import, so making the paths local would break the CodeSandbox and StackBlitz buttons, and pinning a commit SHA would freeze the images and add a maintenance task for no present benefit. All of them resolve today. The weakness was never the URL, it was that nothing connected it to the file it names. Moving or renaming one of those assets would break the published image silently, on the Storybook docsite as well. The content audit now resolves each of those URLs against the working tree, so the failure happens in the commit that moves the file rather than in production. It needs no network, and was verified by moving an asset and confirming two pages fail.
`tabMode="top"` renders the switcher into the same grid area as the page itself (`[grid-area:main]`). Neither element sets `align-self`, so both stretched to fill the row and the switcher became a 23,936px bar with an opaque background and `z-10`, painted over the article. All 209 pages showed nothing but chrome. The content was in the DOM and correct throughout, which is why nothing caught it: links resolved, content audits passed, axe was satisfied, parity matched. Every gate reads the DOM, and presence was never the property that mattered. `yarn test:render` now checks that a page's heading is the element actually hit at its own coordinates, and that a page-appropriate amount of text is visible. Removing the fix fails all four pages and names the covering element. The behaviour is unchanged in fumadocs-ui 16.15.1, so the rule stays until it is fixed upstream.
…e structure Found by looking at the rendered pages rather than the DOM. The headless stories' token stylesheet carries global element rules alongside its custom properties — `body`, `a`, `code`, `button`. Under Storybook those are correct, because the story is the page. Loaded by the site they styled the site: `body` set its own background and colour and, being unlayered, outranked the theme in `@layer base`, so headless pages rendered white-on-black whatever the toggle said. A Vite plugin scopes those rules to the preview subtree, which is what stands in for Storybook's canvas here, and leaves the properties global. The source panel highlighted with `github-light` only, so dark mode put dark text on a dark page. It now emits both palettes and follows the theme. Alongside those: - Search results led to `/docs/docs/...`; the index stored `/docs`-prefixed URLs while the router already mounts at `/docs`. The link checker cannot see this because it reads rendered anchors, not the index. - `Concepts/Introduction` becomes the `/react` landing page, which had been a placeholder saying "Documentation for Fluent UI React v9." twice. The tree had two introductions and the useful one was a level down. - Removed the Component Selector page, whose only story renders `<div>Hello world</div>`. - The theme toggle shared a footer row with the Charts link instead of stretching the sidebar width on its own line, and the Storybook link is gone from the sidebar.
Descriptions are authored as Markdown files and imported as strings, which is correct and matches Storybook's `asset/source` rule: stories combine them before they become `parameters.docs.description.component`. Storybook then renders that string as Markdown. This site printed it verbatim, so headings, lists and fenced code appeared as literal `##`, `-` and backticks on 127 of 209 pages. The rule mirrored Storybook's input and not its output. Rendering happens where the string is consumed rather than at build time, because the `.md` imports have to stay strings for the stories to be able to join them. Fenced code goes through the site's own highlighter, so a snippet in a description looks like the same snippet in the source panel. The highlighter moves to its own module and gains the languages the descriptions use beyond `tsx` — `ts`, `jsx`, `js` and `css`. `buildMarkdown` is deliberately untouched: Copy as Markdown wants the raw string. Raw HTML is passed through rather than escaped, which is what Storybook does with these same strings, and they are files in this repository. The content audit now fails on Markdown left unrendered in prose. Verified by reverting the render and confirming it reports the affected pages.
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.
No description provided.