Repository navigation
docs: greenfield pass on the README and docs site - #133
Merged
Merged
Conversation
Reorder and tighten the front door so a cold reader gets what, why, and
how in the first screen:
- Open on the concrete try/catch pain (catch (error: unknown), and a
thrown Error collapsing to {} over JSON.stringify or an IPC hop)
instead of a comparison to Result libraries the reader may not use.
- Replace the 40-line first example with a ~15-line whole-idea block,
then a real shipping example from Whispering's transcription layer.
- Add a What you give up section: no ? operator, no DI, no concurrency;
reach for Effect if you need them.
- Show the error satisfies never exhaustiveness guard instead of
claiming a bare switch errors on its own.
- Fix accuracy: InferError takes one type param; the cause field
serializes only as well as what you caught; demote query and
standard-schema to one-line mentions; surface logger and testing,
which the README never documented despite heavy real use.
ERROR_HANDLING_GUIDE.md (950 lines) and NAMING_CONVENTION.md duplicated the README and the installable skills, and much of the guide was generic error-handling advice (circuit breakers, metrics) unrelated to the library. Both also documented the wrong two-argument InferError<T, K> signature roughly ten times; the real type takes one type parameter. Their only library-specific content, the discriminated-union-input anti-pattern, already lives in the define-errors skill (its 'Discriminated union inputs' section). Git keeps the bodies recoverable.
The nav listed integrations/nextjs-app-router and integrations/nodejs-express, neither of which exists. The quick-start Note linked to /guides/error-handling and /patterns/framework-integration, also nonexistent. Point them at real pages (core/error-system, integrations/svelte-tanstack).
Rewrite index.mdx to match the README's voice and drop the AI-slop: the emoji Key Benefits list, bold-everything, and the powerful/elegant/ delightful adjectives. Remove the em dash, point new readers at the README for the fast tour, and keep the Mintlify cards (the site's job) with hrefs that all resolve.
Delete pages that the lean README or another page now covers: - core/typescript-patterns, patterns/factory-type-pattern, patterns/error-transformation: orphaned (not in nav), duplicate guidance. - integrations/svelte-tanstack, integrations/react-tanstack: query is a minor feature in real use; the framework-neutral tanstack-query page is the complete canonical version. Consolidate onto it and put it in nav. - case-studies/whispering-architecture: the README now carries the real Whispering example; the full case study was marketing-heavy. Repoint the inbound links (index, quick-start, real-world) and de-slop the now-promoted tanstack-query intro.
result-pattern claimed a bare switch makes TypeScript 'ensure all cases are handled' with no guard, and referenced error.field on a type without that field. Add field to the type, show the canonical never check in default, and state plainly that a plain switch does not enforce this on its own. error-system imported InferError but the chaining example used InferErrors; import the name actually used.
Two pages showed a bare switch and claimed 'TypeScript error if you miss a case'; without a never guard the compiler stays silent. Add the default never check so the claim holds, and say so explicitly. why-name-and-message claimed TypeScript enforces JsonValue fields and that a Date field is a type error. The source enforces only message: string; serializability is a convention. Reframe it as the convention it is, so the commented 'type error' is not itself a falsehood.
Apply the house writing rules to every surviving page: remove all em and en dashes (replaced with the colon, comma, semicolon, period, or parentheses each sentence needed) and the AI-slop adjectives (powerful, elegant, seamless, magic, robust), and normalize the brand to lowercase wellcrafted in prose. Punctuation and word choice only; no arguments, claims, examples, headings, or code logic changed.
One example imported from '@wellcrafted/result/error', which is not a package export. The real subpath is 'wellcrafted/error'.
wellcrafted.dev is not referenced by any deploy config or package homepage, so it was an unverifiable link. The subpath list stands on its own.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Make the thiserror analogy do more work in the README: a namespace is the complete set of ways a function can fail, named up front, which is exactly a Rust error enum (namespace = enum, key = variant, switch = match). Spell out the payoff, the union flows into Result<T, E> and a switch with a never guard catches a forgotten variant, so enumerating up front is what earns the exhaustiveness.
These ship to users and teach agents, so wrong examples propagate:
- result-types: partitionResults returns { oks, errs } of Ok<T>[]/Err<E>[],
not { ok, err } of unwrapped values; and drop a dead TaggedError.Unexpected
factory shorthand for a defineErrors namespace call.
- patterns: a destructured error is the unwrapped body, so returning it from
a Result-returning function is a type error; wrap with Err(error). Also add
a missing NotFound variant that was called but never defined.
- query-factories: defineMutation examples were missing the required
mutationKey.
- error-system: a catch returned a phantom NetworkRequestError; the function and its other variants use NetworkError. - hono-serialization: cast referenced a non-exported TaggedError; the real base type is AnyTaggedError. - from-try-catch: three dead links to migration pages that never existed, repointed to real pages. - developer-experience: a Result-returning example returned a raw destructured error; wrap with Err(error).
- src/README.md: a defineErrors example destructured a non-existent
ValidationErr factory and referenced UnwrapError; the real names are the
single ValidationError factory and UnwrapErr.
- result.ts: the Err JSDoc taught the deleted TaggedError.X({ cause }) API;
point it at a defineErrors factory instead, and drop two em dashes from the
comment.
The explicit-type-arg form tryAsync<User, ApiError> does not compile: the second type parameter is the catch handler's return (R extends Ok<T> | Err<...>), not the error type, and a tagged error does not satisfy that constraint. Real code never passes these args, it infers them. Drop the args across the docs so the examples typecheck the way the library is actually used (the one generic retry example anchors T with a try-return annotation instead). Also, error-system called the shape 'the TaggedError type', but no such type is exported; the base type is AnyTaggedError. Call it the tagged error.
core-concepts surveyed Result, tagged errors, and brand types once-over- lightly, which the README now does tighter and result-pattern/error-system/ brand-types do deeper; it was the sandwiched middle layer. implementation re-typed Result/Ok/Err and reimplemented isOk/isErr/trySync/unwrap as prose, a second copy of src that had already drifted (a wrong trySync signature, a line-count that conflicts with the index). Both earned nothing the README and the deep pages don't already cover. Repoint the inbound cards to the error-system page and drop the nav entries.
developer-experience had a second switch (beyond the one already fixed) commenting 'TypeScript ensures you handle all cases' with no never guard; add the guard so the claim holds. design-principles asserted the same in prose; soften to 'lets you enforce', which is what the never check gives.
The intro said "a query or mutation defined once is callable directly," but queries are not callable: they expose .options plus .fetch()/.ensure(). This contradicted both the README and this page's own line 122. Reword to match the real surface (queries expose .options/.fetch/.ensure; mutations are callable and expose .options).
The skill described resolve backwards ("returns a Result as-is, wraps plain
values in Ok()"). Actual resolve(value: T | Result<T,E>) unwraps Ok to its
data, throws on Err, and passes non-Result values through unchanged.
The package is ESM-only ("type": "module", import-only exports), so the
require() example was non-functional. Source uses const type parameters
(defineErrors<const TConfig ...>), which need TypeScript 5.0+, not 4.5.
The tryAsync catch handlers returned raw tagged objects, which the real catch signature rejects (it must return Ok or Err). They also attached fields the error types never declared, so the examples never compiled. Return the errors via Err(...), widen GatewayError/ApiError to the fields the code actually carries and reads, and narrow the unknown catch arg. Verified with tsc --strict against the library types.
The utility list showed trySync<T, E>/tryAsync<T, E>, but the real second type parameter is the catch-return constraint (R extends Ok<T> | Err<any>), not an error type. Drop the explicit params; callers rely on inference.
This branch was successfully deployed
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.
The docs had three competing homes for the same material: a 14KB README, a 29KB
ERROR_HANDLING_GUIDE.mdplus aNAMING_CONVENTION.md, and a 28-page Mintlify site with ~8 philosophy essays. Each restated the others, some of it was wrong, and the README opened against "Result libraries" (a category most readers landing from npm are not in). This rewrites the front door around how the library is actually used and gives each concept a single home.The weighting comes from the real consumer. Across the Epicenter monorepo (210 files import wellcrafted),
wellcrafted/resultandwellcrafted/errordominate;brand,logger, andtestingare secondary;standard-schemahas zero consumers. The old README documented dead exports (unwrap,isOk,resolveat 0 uses) while omittinglogger(45 uses) and the testing helpers (expectOkat 109). The new README leads with thetry/catchpain an app developer feels, shows a ~15-line "whole idea" block, then a real shipping example lifted from Whispering's transcription layer.It also stops overclaiming. The exhaustiveness section now shows the
error satisfies neverguard instead of implying a bareswitcherrors on a missing case (it does not). The serialization pitch is honest about the one soft spot, the rawcausefield, which serializes only as well as whatever you caught. A "What you give up" section names the tradeoffs out loud: no?operator, no dependency injection, no concurrency, reach for Effect if you need them.The site is slimmed, not deleted. Both root guides are gone (redundant with the README and the installable skills, and wrong about
InferErrorin ten places). Six redundant or orphaned pages are removed, the two framework-specific TanStack pages fold into the neutral one, broken nav entries and dead in-page links are fixed, and a voice pass removes every em and en dash and the AI-slop adjectives across the surviving pages. All philosophy essays stay; only their flatly-false code claims were corrected.How to review this: the README is the load-bearing change, so start there. The slimming and the voice sweep are mechanical and net-negative on line count. The correctness commits (
InferErrorarity, exhaustiveness guards, the serializability convention, one wrong import path) are the ones worth a close read, since they change claims rather than prose.Two calls I made that are worth pushback if you disagree: I kept
getting-started/quick-startandinstallation(a step-by-step tutorial reads as a distinct artifact from the reference README), and I left the softer Rust-comparison wording in the philosophy essays alone as your voice rather than re-arguing it.