examples: add 6 new over-engineering survivors + platform-native guide (#109)

New examples (examples/):
- modal-dialog: <dialog> vs Radix/react-modal
- url-params: URLSearchParams vs query-string
- number-formatting: Intl.NumberFormat vs numeral
- infinite-scroll: IntersectionObserver vs react-infinite-scroll-component
- deep-clone: structuredClone vs lodash.cloneDeep / JSON hack
- group-by: Object.groupBy vs lodash.groupBy

New doc (docs/platform-native.md):
Comprehensive reference of platform-native solutions across HTML elements,
CSS, Browser APIs, Node.js stdlib, Python stdlib, and database features.
Covers 60+ cases where the platform already has what developers reach for
a package to do.
This commit is contained in:
Jesus cornelio
2026-06-19 00:24:29 +02:00
committed by GitHub
parent 7f4dc907fc
commit b345e49385
7 changed files with 437 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
# Platform-Native Solutions
The lazy senior dev's first question is always: *does the platform already do this?*
This document answers that question for the most common cases. Before reaching for a package, scan here. The platform ships with your app for free, doesn't break on updates, and was written by people whose job is exactly that problem.
---
## HTML Elements
Things the browser already has as a form control.
| You think you need | What the platform has |
|---|---|
| Date picker library | `<input type="date">` |
| Time picker library | `<input type="time">` |
| Color picker library | `<input type="color">` |
| Range slider library | `<input type="range">` |
| Progress bar component | `<progress value="70" max="100">` |
| Meter/gauge component | `<meter value="0.7">` |
| Modal/dialog library | `<dialog>` + `dialog.showModal()` |
| Accordion/FAQ component | `<details><summary>Title</summary>…</details>` |
| Tooltip library | `title` attribute + CSS `::before`/`::after` |
| Searchable dropdown | `<input list="id"> <datalist id="id">` |
| Auto-growing textarea | `field-sizing: content` (CSS) |
| Sticky header | `position: sticky; top: 0` (CSS) |
---
## CSS Capabilities
Things developers reach for JavaScript to do.
| You think you need JS for | What CSS has |
|---|---|
| Responsive font size | `font-size: clamp(1rem, 2.5vw, 2rem)` |
| Fluid spacing | `padding: clamp(1rem, 5vw, 3rem)` |
| Dark mode | `@media (prefers-color-scheme: dark)` |
| Reduced motion | `@media (prefers-reduced-motion: reduce)` |
| Responsive layout without breakpoints | `grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))` |
| Component-level responsive design | `@container` queries |
| Global design tokens / theming | CSS custom properties (`--color-primary: #7c3aed`) |
| Smooth scroll | `scroll-behavior: smooth` |
| Scroll-snap carousel | `scroll-snap-type: x mandatory` + `scroll-snap-align: start` |
| Aspect ratio enforcement | `aspect-ratio: 16 / 9` |
| Truncate text with ellipsis | `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` |
| Multi-line text clamp | `-webkit-line-clamp: 3` |
| CSS cascade layers (style isolation) | `@layer base, components, utilities` |
| Nested CSS selectors | Native CSS nesting (no preprocessor needed) |
| `has()` parent selector | `:has(input:checked)` |
---
## JavaScript / Browser APIs
Libraries people install that the runtime already ships.
| You think you need | What the platform has |
|---|---|
| `query-string` / `qs` | `new URLSearchParams(location.search)` |
| `lodash.clonedeep` | `structuredClone(obj)` |
| `lodash.groupby` | `Object.groupBy(arr, fn)` |
| `lodash.debounce` | — see debounce one-liner below |
| `numeral` / `accounting` | `new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })` |
| `date-fns` format | `new Intl.DateTimeFormat("en-US", { dateStyle: "long" }).format(date)` |
| `date-fns` relative time | `new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(-3, "day")` |
| `plural` / `i18n` plurals | `new Intl.PluralRules("en-US").select(count)` |
| `clipboard.js` | `navigator.clipboard.writeText(text)` |
| `uuid` (v4) | `crypto.randomUUID()` |
| Infinite scroll library | `new IntersectionObserver(cb).observe(sentinel)` |
| Resize listener library | `new ResizeObserver(cb).observe(element)` |
| DOM mutation watcher | `new MutationObserver(cb).observe(el, options)` |
| `uuid-validate` | `/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)` |
| `is-online` / `connectivity check` | `navigator.onLine` + `online`/`offline` events |
| `sharesheet` library | `navigator.share({ title, text, url })` |
| `store.js` / `localForage` (simple case) | `localStorage.setItem(key, JSON.stringify(val))` |
| Abort fetch on timeout | `AbortSignal.timeout(5000)` passed to `fetch` |
| Custom event bus | `new EventTarget()` / `dispatchEvent(new CustomEvent("x", { detail }))` |
**Debounce one-liner** (no library):
```js
// ponytail: 3 lines beats a dependency
let t;
const debounce = (fn, ms) => (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
```
---
## Node.js Standard Library
Packages that wrap Node built-ins.
| You think you need | What Node has |
|---|---|
| `mkdirp` | `fs.mkdirSync(path, { recursive: true })` |
| `rimraf` | `fs.rmSync(path, { recursive: true, force: true })` |
| `make-dir` | `fs.mkdirSync(path, { recursive: true })` |
| `slash` (win paths) | `path.posix` or `path.normalize()` |
| `uuid` (v4) | `crypto.randomUUID()` |
| `ms` (parse duration strings) | — keep `ms`, it's genuinely useful and tiny |
| `is-stream` | `val instanceof stream.Readable` |
| `object-assign` | `Object.assign()` / spread |
| `array-uniq` | `[...new Set(arr)]` |
| `array-flatten` | `arr.flat(Infinity)` |
| `flat` | `arr.flat(depth)` |
| `path-exists` | `fs.existsSync(path)` |
| `load-json-file` | `JSON.parse(fs.readFileSync(path, "utf8"))` |
| `write-json-file` | `fs.writeFileSync(path, JSON.stringify(obj, null, 2))` |
| `pkg-dir` | `path.resolve(__dirname, "..")` / `import.meta.dirname` |
---
## Python Standard Library
Packages that wrap what Python already ships.
| You think you need | What Python has |
|---|---|
| `python-dateutil` (basic parsing) | `datetime.fromisoformat()` (Python 3.7+) |
| `pytz` | `zoneinfo.ZoneInfo("America/New_York")` (Python 3.9+) |
| `attrs` (simple data classes) | `@dataclass` |
| `six` | — drop it, Python 2 is gone |
| `pathlib2` | `pathlib.Path` (built-in since Python 3.4) |
| `enum34` | `enum.Enum` (built-in since Python 3.4) |
| `typing_extensions` (common types) | `from __future__ import annotations` + built-in generics |
| `simplejson` (basic use) | `json` (stdlib) |
| `requests` (simple GET) | `urllib.request.urlopen(url)``requests` for anything real |
| `click` (single command) | `argparse` (stdlib) |
| `mergedeep` | `dict \| other_dict` (Python 3.9+) |
| `more-itertools` (basic) | `itertools` (stdlib): `chain`, `islice`, `groupby`, `product` |
| `toolz` (basic) | `functools`: `lru_cache`, `partial`, `reduce` |
| `tabulate` (dev/debug only) | `pprint.pprint()` for quick inspection |
---
## Database
Things the application layer implements that the database already does.
| You think you need app code for | What the database has |
|---|---|
| Pagination offset/limit | `LIMIT 20 OFFSET 40` |
| Running totals | `SUM(...) OVER (ORDER BY date)` (window function) |
| Rank within group | `RANK() OVER (PARTITION BY category ORDER BY score DESC)` |
| Pivot / cross-tab | `FILTER (WHERE ...)` + conditional aggregation |
| Deduplication | `SELECT DISTINCT` / `ON CONFLICT DO NOTHING` |
| Soft-delete filtering | Generated column + partial index |
| Tree traversal | Recursive CTE (`WITH RECURSIVE`) |
| Full-text search (basic) | `tsvector` / `MATCH AGAINST` / `FTS5` |
| JSON storage + query | `jsonb` (Postgres) / `JSON_EXTRACT` (SQLite/MySQL) |
| UUID generation | `gen_random_uuid()` (Postgres) / `UUID()` (MySQL) |
| Timestamps on insert/update | `DEFAULT now()` + trigger or `ON UPDATE CURRENT_TIMESTAMP` |
| Enforce uniqueness | `UNIQUE` constraint — not application-level checks |
| Enforce referential integrity | `FOREIGN KEY` — not application-level checks |
| Enforce value ranges | `CHECK (price > 0)` — not application-level validation |
---
## The Pattern
Across every layer, the pattern is the same:
```
Platform team spends years solving the problem.
Package author wraps it.
You install the wrapper.
The wrapper goes unmaintained.
You debug the wrapper.
```
Skip the wrapper. The platform ships with your app for free.
When the native solution is genuinely insufficient (old browser support, edge cases it doesn't handle, ergonomics that matter at scale), the library earns its place. Install it then — not before.
+31
View File
@@ -0,0 +1,31 @@
# Deep Clone
**Task:** "Deep clone this object."
## Without Ponytail
```bash
npm install lodash
```
```js
import { cloneDeep } from "lodash";
const copy = cloneDeep(original);
```
Or the classic hack:
```js
// fragile: loses Date, undefined, Map, Set, circular refs, functions
const copy = JSON.parse(JSON.stringify(original));
```
## With Ponytail
```js
// ponytail: structuredClone does this
const copy = structuredClone(original);
```
**1 dependency (or a fragile hack) → 1 built-in.** `structuredClone` handles `Date`, `Map`, `Set`, `ArrayBuffer`, `RegExp`, circular references, and more — everything `JSON.parse/stringify` silently drops. Available in every browser since 2022 and Node.js since v17. Pull lodash in when you need the rest of it, not for one function.
+35
View File
@@ -0,0 +1,35 @@
# Group By
**Task:** "Group this array of objects by a key."
## Without Ponytail
```bash
npm install lodash
```
```js
import { groupBy } from "lodash";
const byStatus = groupBy(orders, "status");
// → { pending: [...], shipped: [...], delivered: [...] }
```
Or the hand-rolled version:
```js
const byStatus = orders.reduce((acc, order) => {
(acc[order.status] ??= []).push(order);
return acc;
}, {});
```
## With Ponytail
```js
// ponytail: Object.groupBy does this
const byStatus = Object.groupBy(orders, order => order.status);
// → { pending: [...], shipped: [...], delivered: [...] }
```
**1 dependency (or a reduce) → 1 built-in.** `Object.groupBy` shipped in Chrome 117, Firefox 119, Safari 17.4, Node.js 21. If you need a `Map` instead of a plain object: `Map.groupBy(orders, o => o.status)`. Check your target runtime; if you need IE11 or old Node, the `reduce` one-liner is still the right call — not lodash.
+58
View File
@@ -0,0 +1,58 @@
# Infinite Scroll
**Task:** "Load more items when the user scrolls to the bottom."
## Without Ponytail
```bash
npm install react-infinite-scroll-component
```
```jsx
import InfiniteScroll from "react-infinite-scroll-component";
export function Feed({ items, fetchMore, hasMore }) {
return (
<InfiniteScroll
dataLength={items.length}
next={fetchMore}
hasMore={hasMore}
loader={<Spinner />}
endMessage={<p>No more items</p>}
scrollThreshold={0.9}
>
{items.map(item => <Card key={item.id} item={item} />)}
</InfiniteScroll>
);
}
```
A dependency to watch scroll position and fire a callback.
## With Ponytail
```jsx
// ponytail: IntersectionObserver does this, no scroll listener needed
import { useEffect, useRef } from "react";
export function Feed({ items, fetchMore, hasMore }) {
const sentinel = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && hasMore) fetchMore();
});
if (sentinel.current) observer.observe(sentinel.current);
return () => observer.disconnect();
}, [hasMore, fetchMore]);
return (
<>
{items.map(item => <Card key={item.id} item={item} />)}
<div ref={sentinel} />
</>
);
}
```
**1 dependency → 0 dependencies.** `IntersectionObserver` fires only when the sentinel enters the viewport — no scroll event, no throttling, no jank. Ships in every browser. The library wraps exactly this API.
+62
View File
@@ -0,0 +1,62 @@
# Modal Dialog
**Task:** "Add a modal dialog for the delete confirmation."
## Without Ponytail
```bash
npm install @radix-ui/react-dialog
# or: npm install react-modal
```
```jsx
import * as Dialog from "@radix-ui/react-dialog";
import { useState } from "react";
export function DeleteModal({ onConfirm, onCancel }) {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button className="btn-danger">Delete</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title>Confirm deletion</Dialog.Title>
<Dialog.Description>This action cannot be undone.</Dialog.Description>
<div className="dialog-actions">
<Dialog.Close asChild>
<button onClick={onCancel}>Cancel</button>
</Dialog.Close>
<button className="btn-danger" onClick={onConfirm}>Delete</button>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
```
A dependency, a portal, an overlay, a root, a trigger, a content wrapper — to show a box with two buttons.
## With Ponytail
```html
<!-- ponytail: browser has one, with focus trapping and backdrop built in -->
<dialog id="confirm-delete">
<p>This action cannot be undone.</p>
<button id="cancel">Cancel</button>
<button id="confirm">Delete</button>
</dialog>
```
```js
const dialog = document.getElementById("confirm-delete");
document.getElementById("cancel").onclick = () => dialog.close();
document.getElementById("confirm").onclick = () => { onConfirm(); dialog.close(); };
// Open it:
dialog.showModal();
```
**1 dependency + 30 lines → 0 dependencies + 8 lines.** The native `<dialog>` traps focus automatically, closes on Escape, renders a backdrop via `::backdrop`, and is accessible by default. All browsers since 2022. The library was solving a problem the platform solved.
+37
View File
@@ -0,0 +1,37 @@
# Number Formatting
**Task:** "Format numbers as currency and with thousand separators."
## Without Ponytail
```bash
npm install numeral
# or: npm install accounting
```
```js
import numeral from "numeral";
numeral(1234567.89).format("$1,234.00"); // "$1,234,567.89"
numeral(0.745).format("0.0%"); // "74.5%"
numeral(1500).format("0.0a"); // "1.5k"
```
## With Ponytail
```js
// ponytail: Intl.NumberFormat does this, locale-aware
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
.format(1234567.89);
// → "$1,234,567.89"
new Intl.NumberFormat("en-US", { style: "percent" })
.format(0.745);
// → "74.5%"
new Intl.NumberFormat("en-US", { notation: "compact" })
.format(1500);
// → "1.5K"
```
**1 dependency → 0 dependencies.** `Intl.NumberFormat` is built into every JS runtime, handles every locale correctly, and gets currency symbols, decimal separators, and grouping right for any market without a lookup table. A library that hardcodes formats will always be wrong for someone.
+41
View File
@@ -0,0 +1,41 @@
# URL Parameters
**Task:** "Parse and build URL query strings."
## Without Ponytail
```bash
npm install query-string
# 4.5 kB gzipped, 3.5M downloads/week
```
```js
import qs from "query-string";
// Parse
const params = qs.parse(location.search);
// → { page: "2", sort: "name", tags: ["js", "css"] }
// Build
const url = qs.stringify({ page: 2, sort: "name", tags: ["js", "css"] });
// → "page=2&sort=name&tags=js&tags=css"
```
## With Ponytail
```js
// ponytail: URLSearchParams does this
const params = new URLSearchParams(location.search);
// Read
params.get("page"); // "2"
params.getAll("tags"); // ["js", "css"]
// Build
const out = new URLSearchParams({ page: 2, sort: "name" });
out.append("tags", "js");
out.append("tags", "css");
out.toString(); // "page=2&sort=name&tags=js&tags=css"
```
**1 dependency → 0 dependencies.** `URLSearchParams` is in every browser and in Node.js since v10. It handles encoding, repeated keys, and iteration. The package was a polyfill for an API that has shipped everywhere for years.