Skip to content

Split API reference out of README, fix npm listing, drop two no-op exports - #1

Merged
sovrin merged 3 commits into
masterfrom
sovrin/readme-npm-layout
Sep 6, 2026
Merged

sovrin merged 3 commits into
masterfrom
sovrin/readme-npm-layout

Conversation

@sovrin

@sovrin sovrin commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The README tried to be the full API reference — 1441 lines, a flat 129-entry TOC and one <details> block per export — so it is now a landing page (pitch, quick start, an intent-keyed "Which function do I need?" cheat sheet, and a one-line-per-export index), with the per-export reference generated into doc/api/*.md under real headings whose anchors survive npm's sanitizer stripping id attributes.

package.json had no description, so the registry fell back to the README's first non-heading line and stored the floating logo <img> tag as the package description; it now has a real description, 13 keywords instead of 3, homepage/bugs, an absolute logo URL (doc/ is npmignored, so the relative path could never resolve from the tarball), and README.MD is renamed to README.md to match the files array.

bin/generate-readme.mjs now emits both outputs from doc/README.template.md and the new doc/api.template.md, and warns when an export is missing from the hand-written cheat sheet.

Two exports were removed: forward, the identity function on Result that was called in 8 places and exported publicly, and attempt, which claimed to handle backtracking but could not — Failure carries no position field, so backtracking is already unconditional — with that guarantee now documented in the README's core concepts instead.

All 1168 tests pass, no type errors, lint clean.

🤖 Generated with Claude Code

sovrin and others added 3 commits September 6, 2026 12:46
The README tried to be the full API reference: 1441 lines, a 129-entry
flat TOC, then one <details> block per export in alphabetical order.
Alphabetical order only helps once you already know the name, which is
the one thing you don't know with 127 exports. On npmjs.com it was worse
— the sanitizer strips the id attributes those TOC links pointed at, so
every one of them was dead.

Split it in two. README.md is now a landing page: pitch, install, a
runnable example, a "Which function do I need?" cheat sheet keyed on
intent rather than name, core concepts, and a one-line-per-export index
grouped by module. The per-export reference with full descriptions and
examples moves to doc/api/*.md, generated from the same JSDoc into real
headings so the anchors survive both renderers.

generate-readme.mjs emits both from doc/README.template.md and the new
doc/api.template.md. The cheat sheet is hand-written, so it also warns
when an export is missing from it — combinators land often enough that
it would rot silently otherwise.

Also fixes the npm listing itself. package.json had no description, so
the registry fell back to the README's first non-heading line and stored
the floating logo tag as the package description:

    "description": "<img src=\"doc/logo.png\" style=\"margin-left: 16px\" ...>"

Add a real description, expand keywords from 3 to 13, add homepage and
bugs, point the logo at an absolute URL (doc/ is npmignored, so the
relative path could never resolve from the tarball), and rename
README.MD to README.md to match both the files array and what the
generator writes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
forward was the identity function on Result:

    export const forward = <T>(result: Result<T>): Result<T> => result;

It was called in 8 places as a marker meaning "pass this through
unchanged". Replacing every call with the result itself typechecks clean
and leaves all tests passing, so it was carrying nothing — just an extra
import and an extra indirection per call site.

It was also exported from the unitas entry point, where a consumer has
no use for it: an identity helper on a public surface only suggests that
pass-through needs one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
attempt claimed to "wrap a parser to handle backtracking on failure",
but it cannot: a Failure is { ok: false; error?: string }, with no field
to record a position, and combinators hand every alternative the same
input string they started with. sequence discards its local remaining
and returns a bare failure(). Since create is itself the identity
function, attempt(p) was one extra closure around p and nothing else.

It was not accidentally a no-op — it is un-implementable in this design
without changing the Failure type. Its own test, "should backtrack on
failure", only asserted that a failing parser fails, which holds for any
parser at all.

Keeping it as a documented no-op would be the worse option: someone
arriving from Parsec writes attempt(p) expecting it to change behaviour
and silently gets nothing. Remove it and explain the actual guarantee in
the README's core concepts, which is where they will look for try.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sovrin
sovrin merged commit a0e8a52 into master Sep 6, 2026
4 checks passed
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