Skip to content

docs: greenfield pass on the README and docs site - #133

Merged
braden-w merged 22 commits into
mainfrom
docs/greenfield-pass
Jun 26, 2026
Merged

braden-w merged 22 commits into
mainfrom
docs/greenfield-pass

Conversation

@braden-w

Copy link
Copy Markdown
Collaborator

The docs had three competing homes for the same material: a 14KB README, a 29KB ERROR_HANDLING_GUIDE.md plus a NAMING_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/result and wellcrafted/error dominate; brand, logger, and testing are secondary; standard-schema has zero consumers. The old README documented dead exports (unwrap, isOk, resolve at 0 uses) while omitting logger (45 uses) and the testing helpers (expectOk at 109). The new README leads with the try/catch pain 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 never guard instead of implying a bare switch errors on a missing case (it does not). The serialization pitch is honest about the one soft spot, the raw cause field, 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 InferError in 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 (InferError arity, 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-start and installation (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.

braden-w added 10 commits June 21, 2026 00:32
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.
@mintlify

mintlify Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
epicenter 🟢 Ready View Preview Jun 21, 2026, 8:15 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@mintlify

mintlify Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
wellcrafted 🟢 Ready View Preview Jun 21, 2026, 8:15 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

braden-w added 4 commits June 21, 2026 01:37
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.
braden-w added 2 commits June 21, 2026 08:28
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.
braden-w added 3 commits June 26, 2026 13:47
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.
braden-w added 2 commits June 26, 2026 13:59
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.
@braden-w
braden-w merged commit 362c171 into main Jun 26, 2026
2 checks passed
@braden-w
braden-w deleted the docs/greenfield-pass branch June 26, 2026 22:06

This branch was successfully deployed

1 active deployment
staging - docs — 91faf536 Deployed Jun 26, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant