Skip to content

Commit a0877f6

Browse files
authored
docs: cut filler from README and docs site (#786)
* docs: cut AI-generated filler from README and docs site Rewrite the prose across the README, docs site, CONTRIBUTING, and SECURITY to say what the library does instead of how it feels. - README: drop the emoji and puffery from the feature list, replace the MDN-boilerplate option descriptions with one-sentence versions, and rewrite the testing and polyfill sections in plain language. - overview.mdx: replace the "Built for production React" marketing bullets with concrete mechanisms and numbers. - ssr.mdx: remove three copies of the same initialInView caveat and an orphan paragraph restating fallbackInView vs defaultFallbackInView. - configuration.mdx, core-apis.mdx, testing guides: split dense sentences, remove em dashes, drop "React surface" and other jargon. - CONTRIBUTING, SECURITY: sentence-case headings, trim the filler. Also fixes issues found along the way: broken indentation in the render-props sample, the typos "explictly", "intersecing" and "test files were you actively import", and the British "specialised" in an otherwise US-spelled doc set. Renaming the "Stop, pause, or choose a callback API" heading changed its anchor, so the link in core-apis.mdx is updated to match. Docs site builds clean. * docs: unslop the landing page copy The landing page was in better shape than the markdown, so this is a smaller pass over the copy that was still doing marketing instead of explaining. - The demo feed teasers were the worst of it: "Design systems that travel", "A quiet note on shipping", "A scroll worth observing". Nine labels of evocative filler that would read identically in any other project. They are scroll filler, so number them. That also makes scroll position legible in a demo whose whole point is scrolling. - "Ideal for analytics, prefetching, or logging" becomes "Use it for". - The closing headline "Add a kilobyte. Ship the viewport." had a second half that does not mean anything. Now "Add a kilobyte. Know what is on screen.", which echoes the hero. - Page title dropped a duplicate "for React"; the product name already says it. - Site description traded "lightweight" for what the library actually does. - Em dashes out of the section-divider comments and CSS, per house style. Left the hero, feature grid, and section headings alone. They already make specific claims and have a voice. * feat(docs): demo lazy loading on the landing page The page gave impressions a full interactive section but never showed lazy loading, which is one of the main reasons people reach for this library. It appeared once, as a hyphenated word in the hero lead. Adds a section between the API scrollspy and the impression strip: - Six tiles that reserve their space, then mount their <img> only once the observer reports them within 200px of the viewport. No image element exists in the DOM before that, so the deferral is real rather than a CSS trick. - A counter showing how many have been requested, plus the useInView call that drives it. - A note pointing plain images at loading="lazy" instead, so the section does not oversell the observer for cases the platform already handles. The artwork is inline SVG data URIs. A tile that has not been reached costs nothing and the page pulls no extra files. Also leads with the use cases now that they have somewhere to point: - Hero lead opens with "Reveal on scroll, lazy-load images, track impressions, build infinite lists" before the technical framing. - README intro names scroll animations, lazy loading, impressions, and infinite scroll. - Site description swaps "lightweight" for those same use cases, which is also closer to what people search for. The index badge overlays the frame, so it needs to read against the empty placeholder and the loaded artwork both. It uses the theme foreground when deferred and white once loaded; a single colour only worked for one of them. Verified in the browser at 375, 768, and 1280, in both themes: deferred state holds 0/6 with no img elements, loading flips all six, the code panel does not overflow its column, and the page never scrolls horizontally. * docs: simplify the InfiniteList recipe You don't need the effect. An empty list puts the sentinel inside the viewport, so the observer already asks for the first page. The effect was a second copy of the fetch, the error handling, and the loading flag, racing the observer for the same request. The `loading: true` initial state only existed to referee that race, so it goes too. Four states become three. `loading` and `error` booleans could both be set at once, which is not a state this component has; one `status` union removes it and lets the button derive its own label. The `useCallback` goes as well: the hook reads `onChange` from a ref, so a plain function is enough. The two buttons merge into one that says "Try again" after a failure. Verified against the browser's own IntersectionObserver, not the mock, since the mock does not replay state to a newly created observer and hides the behavior this recipe depends on. Six cases: first page loads with no effect, a short page keeps filling, each page is requested exactly once, observation stops on the last page, the button recovers from an error, and a failure does not turn into a scroll-retry loop. The re-arm is worth knowing about. Flipping `skip` drops and recreates the observer, which is what lets a page too short to push the sentinel out of view keep loading. The prose now says so instead of describing `skip` as only a duplicate-request guard. * docs: drop void and async from the InfiniteList recipe `void` was there to quiet a floating-promise lint rule, and `async` promised a result nobody awaits. `loadMore` now starts the request and returns nothing, so the observer calls `loadMore()` and the button takes `onClick={loadMore}` directly. The two-argument `.then` is deliberate. `.catch` after it would also swallow a bug thrown by the state updates in the success path and report it to the user as a failed request. Same slop in the lazy-loading section I added two commits ago, caught while looking: LazyTile reported visibility to its parent through a useEffect on `inView`, which is the effect the recipe rewrite just removed. It uses `onChange` now. That fires exactly once under `triggerOnce`, so the parent counts with a number instead of an id array with an includes() dedupe guard, and the useCallback around it goes too. Verified against the browser's own observer, since the docs preview pane was hidden and a hidden page delivers no intersections at all: the recipe still passes all seven cases, and the tiles start deferred with no img in the DOM, count to exactly six on scroll, and do not double-count when scrolled away and back. * docs: use useTransition in the InfiniteList recipe Yes, it is the right hook, and for a better reason than ergonomics. The hand-rolled status had a real bug. When `loadPage` resolves from a warm cache without ever yielding, React batches the update that sets the loading flag together with the one that clears it. `skip` never changes, so the observer never re-arms and the list stops after one page. Measured on the same input: the status version requests [0], this one requests [0, 1, 2, 3, 4]. `isPending` is raised by React when `startTransition` runs and lowered when the awaited work settles, so it renders either way. It also removes the `setLoading(false)` that has to be repeated on every exit path, and marking the append as a transition keeps a list of hundreds of rows from blocking a click. It also answers the floating-promise objection properly rather than by deleting `async`: `startTransition` awaits the function it is given, so the async work now has an owner. Async transitions need React 19, so the recipe carries a note telling React 18 readers to track the status themselves. Verified against the browser's own observer, eight cases: first page with no effect, a short page keeps filling, a warm cache keeps filling, each page requested exactly once, observation stops on the last page, the live region announces pending then failure, the button recovers, and a failure does not become a scroll-retry loop. * docs: unslop the InfiniteList prose, and say why the catch stays Cuts the tells from the explanation. "And that is the point" was patting itself on the back. "React raises isPending and lowers it" dressed up set and clear. "Marking the append as a transition is worth it on its own:" leaned on a colon to join two thoughts that wanted to be one sentence. "A hand-rolled loading boolean is also less reliable here" hedged about something that is simply broken, so it now says breaks. Also answers the obvious question about the try/catch, since the transition looks like it should handle the error itself. It does not. useTransition returns isPending and startTransition, and nothing else. Measured what an uncaught throw actually does: it reaches the nearest error boundary, the boundary swaps in its fallback, and the list unmounts with every page already loaded inside it. Failing on page five would discard pages one through four. The catch is what keeps the rows on screen and leaves the button as a retry. * docs: trim the InfiniteList prose 345 words down to 94. Most of it was arguing with an imagined reviewer rather than telling a reader anything: why there is no effect, why a hand-rolled loading boolean would be worse, why the try/catch has to stay, why the button earns its place. That is a record of how the example was arrived at, and none of it helps someone reading the example. What survives is the part a reader cannot infer from the code. The sentinel loads the first page. Flipping `skip` recreates the observer, which is what keeps a short page filling. Tune `rootMargin`. Keep the button. The other recipes on the page close in 53 to 85 words. This one was four times longer than any of them. * docs: stop presupposing an argument about the button "Keep the button" answers an objection the reader never made. Nobody proposed removing it; it is right there in the example. The rule on its own carries the point, so the sentence is now just "Scrolling should never be the only way to load more." The caption above the code had the same problem, telling the reader to "keep" a button they have not added yet. It says add. * docs: two leftovers from the same habit Swept the rest of the docs for prose that argues with a reader who has not said anything. Two hits, both milder than the InfiniteList case. configuration.mdx warned that `scrollMargin` is not a substitute for `rootMargin` in the section on where to observe, having already drawn that distinction 30 lines earlier where `rootMargin` is introduced. The second pass now only carries what is new, the syntax and the gotchas. The v2 guide had a heading, "Test it deliberately", where deliberately is a judgement rather than a description. It says "Test it in a real browser", which is the actual advice. Nothing links to the old anchor. The rest came back clean. README and CONTRIBUTING have no instances. The "keep" phrasings elsewhere are ordinary instructions about the reader's own code, and the "instead of" ones are real either/or choices, Browser Mode against jsdom and the mock against real layout. Section lengths now run 60 to 145 words with no outlier. * docs: say what things are, not what they are not Three sentences defined something by naming a category it does not belong to. Each one assumed the reader was mid-mistake, and none of them said anything actionable, since the real advice was always in the sentences around them. "A fallback is not a loading strategy" was a topic sentence for a paragraph that already gives the advice. Deleted; the paragraph reads better without it. "Reach for it when a scroller inside the root clips the target, not when you want to adjust the viewport" both repeated the sentence before it and corrected a reader who had not done anything. `scrollMargin` is now described once, and the section on where to observe is where you find out when to use it. "It is not a layout ratio" guarded a genuine trap, since `mockAllIsIntersecting(0.3)` does look like thirty percent visible. Naming what the number does select, which of your configured thresholds the observer crossed, closes the same trap without the correction. Left the README FAQ line about `root` not being the viewport. That section answers a reader who has already hit the problem and gone looking, so telling them what is not true is the answer they came for.
1 parent 322451a commit a0877f6

19 files changed

Lines changed: 670 additions & 398 deletions

CONTRIBUTING.md

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# Contribution Guidelines
1+
# Contribution guidelines
22

3-
Welcome to `react-intersection-observer`! I'm thrilled that you're interested in
4-
contributing. Here are some guidelines to help you get started.
3+
Thanks for wanting to contribute to `react-intersection-observer`. Here's what
4+
you need to know to get started.
55

66
The codebase is written in TypeScript and uses PNPM workspaces:
77

@@ -12,22 +12,22 @@ The codebase is written in TypeScript and uses PNPM workspaces:
1212

1313
## Development
1414

15-
Start by forking the repository, and after cloning it locally you can install
16-
the dependencies using [PNPM](https://pnpm.io/):
15+
Fork the repository, clone it locally, and install the dependencies with
16+
[PNPM](https://pnpm.io/):
1717

1818
```shell
1919
pnpm install
2020
```
2121

22-
Then you can start the development surfaces with the `dev` task:
22+
Then start both apps with the `dev` task:
2323

2424
```shell
2525
pnpm dev
2626
```
2727

28-
Use `pnpm dev:storybook` or `pnpm dev:docs` to start one surface at a time.
28+
Use `pnpm dev:storybook` or `pnpm dev:docs` to start one at a time.
2929

30-
## Semantic Versioning
30+
## Semantic versioning
3131

3232
`react-intersection-observer` follows Semantic Versioning 2.0 as defined at
3333
http://semver.org. This means that releases will be numbered with the following
@@ -39,52 +39,47 @@ format:
3939
- Backwards-compatible enhancements will increment the minor version.
4040
- Bug fixes and documentation changes will increment the patch version.
4141

42-
## Pull Request Process
42+
## Pull requests
4343

44-
Fork the repository and create a branch for your feature/bug fix.
44+
Create a branch on your fork for the fix or feature, then:
4545

46-
- Add tests for your feature/bug fix.
47-
- Ensure that all tests pass before submitting your pull request.
48-
- Update the README.md file if necessary.
49-
- Ensure that your commits follow the conventions outlined in the next section.
46+
- Add tests for the change.
47+
- Make sure all tests pass.
48+
- Update `README.md` if the change affects it.
49+
- Follow the commit conventions below.
5050

51-
### Commit Message Conventions
51+
### Commit message conventions
5252

53-
- We follow the
54-
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
55-
Conventions, so the generated release notes stay readable. This means that
56-
your commit messages should have the following format:
53+
Commits follow
54+
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), so the
55+
generated release notes stay readable:
5756

5857
`<type>: <subject>`
5958

60-
Here's what each part of the commit message means:
59+
- `<type>` is the kind of change. Use `feat` for new features, `fix` for bug
60+
fixes, `docs` for documentation, and `chore` for everything that doesn't touch
61+
the code itself, such as dependency updates.
62+
- `<subject>` is a short description of the change.
6163

62-
- `<type>`: The type of change that you're committing. Valid types include
63-
`feat` for new features, `fix` for bug fixes, `docs` for documentation
64-
changes, and `chore` for changes that don't affect the code itself (e.g.
65-
updating dependencies).
66-
- `<subject>`: A short description of the change.
64+
### Code style and linting
6765

68-
### Code Style and Linting
69-
70-
`react-intersection-observer` uses [Biome](https://biomejs.dev/) for code
71-
formatting and linting. Please ensure that your changes are formatted with Biome before
72-
submitting your pull request.
66+
`react-intersection-observer` uses [Biome](https://biomejs.dev/) for formatting
67+
and linting. Format your changes with Biome before opening a pull request.
7368

7469
### Testing
7570

7671
`react-intersection-observer` uses [Vitest](https://vitest.dev/) for testing.
77-
Please ensure that your changes are covered by tests, and that all tests pass
78-
before submitting your pull request.
72+
Cover your changes with tests, and make sure the whole suite passes before
73+
opening a pull request.
7974

80-
You can run the package tests with the `test` task. Component tests run in
81-
Vitest Browser Mode with Playwright; SSR tests run in a separate Node project.
75+
Run the package tests with the `test` task. Component tests run in Vitest
76+
Browser Mode with Playwright, and SSR tests run in a separate Node project.
8277

8378
```shell
8479
pnpm test
8580
```
8681

87-
Build every published and documentation surface with:
82+
Build the package and both apps with:
8883

8984
```shell
9085
pnpm build:all
@@ -107,6 +102,6 @@ The workflow bumps the version, commits and tags it, builds the package,
107102
publishes it to npm, and creates a GitHub release with generated notes.
108103

109104
`main` is protected, so the version commit is pushed with a short-lived token
110-
minted from a GitHub App that is listed as a bypass actor on the branch
111-
ruleset. The app's id lives in the `RELEASE_APP_ID` variable and its private key
112-
in the `RELEASE_APP_KEY` secret.
105+
minted from a GitHub App listed as a bypass actor on the branch ruleset. The
106+
app's id lives in the `RELEASE_APP_ID` variable, and its private key in the
107+
`RELEASE_APP_KEY` secret.

0 commit comments

Comments
 (0)