Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f3e4e50
refactor: resolve each group once and let it carry the roving stop
mixedrays Oct 15, 2026
089332b
Merge pull request #30 from mixedrays/refactor/deepen-group-module
mixedrays Oct 15, 2026
1b1c1aa
refactor(docs): define each demo in one place
mixedrays Oct 15, 2026
ea2b5f6
docs: add comprehensive glossary for keyrove terminology
mixedrays Oct 15, 2026
b3c3479
refactor(docs): render every page projection from one parse
mixedrays Oct 16, 2026
7fd93e4
refactor: walk group nesting in one module
mixedrays Oct 16, 2026
0f85d15
Merge pull request #33 from mixedrays/refactor/group-nesting-walk
mixedrays Oct 16, 2026
1509c9d
Merge pull request #31 from mixedrays/refactor/demo-definition
mixedrays Oct 16, 2026
1c452bb
Merge pull request #32 from mixedrays/refactor/page-render
mixedrays Oct 16, 2026
dd1b8ad
refactor(docs): keep the placeholder registry in one place
mixedrays Oct 16, 2026
12be994
refactor(docs): answer every site address in one module
mixedrays Oct 16, 2026
152879c
docs: strip the markup the landing page's tabs throw away
mixedrays Oct 16, 2026
6217011
refactor(docs): build the sidebars' keyboard rails from one module
mixedrays Oct 16, 2026
599fdde
Merge pull request #36 from mixedrays/refactor/keyboard-rail
mixedrays Oct 16, 2026
7a37512
Merge pull request #35 from mixedrays/refactor/placeholder-registry
mixedrays Oct 16, 2026
5f5ce70
Merge pull request #34 from mixedrays/refactor/site-addresses
mixedrays Oct 16, 2026
9e1a4e5
chore: remove unused code and assets
mixedrays Oct 16, 2026
4afb19a
Merge branch 'develop' of https://github.com/mixedrays/keyrove into d…
mixedrays Oct 16, 2026
227ad1f
clean up conflicts merge issues
mixedrays Oct 16, 2026
2490860
chore: move the docs site and benchmark into apps/
mixedrays Oct 16, 2026
8002ab9
feat: stop group walks at the listener's element
mixedrays Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ jobs:
# TanStack Start writes the prerendered site to `dist/client`; the
# server build beside it in `dist/server` only runs the prerender.
command: >-
pages deploy packages/docs/dist/client
pages deploy apps/docs/dist/client
--project-name=keyrove
--branch=main
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pnpm-lock.yaml
CHANGELOG.md

# Generated by TanStack Router on every dev start and build.
packages/docs/src/routeTree.gen.ts
apps/docs/src/routeTree.gen.ts
20 changes: 18 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ pnpm install
pnpm dev
```

## Branches

Work happens on `develop`; `main` only receives merges from `develop`. Start
every change from an up-to-date `develop`:

```sh
git switch develop
git pull
git switch -c feat/short-description
```

Prefix the branch name with the type of change, as in
[commit messages](#commit-messages): `feat/`, `fix/`, `refactor/`, `docs/`,
`chore/`. When the change is ready, push the branch and open a pull request into
`develop`, not `main`.

## Before opening a pull request

Run the same checks CI runs:
Expand All @@ -33,7 +49,7 @@ pnpm build
There are two READMEs. The root `README.md` is a short entry point that links to
the docs site; `packages/keyrove/README.md` is the detailed one published to
npm. A change to the public API usually touches the package README and the docs
under `packages/docs/content`, and the root README only if the basic example
under `apps/docs/content`, and the root README only if the basic example
changes.

## Commit messages
Expand All @@ -51,7 +67,7 @@ anything large.

## Still to be written

- Which branch pull requests should target, and how they are reviewed.
- How pull requests are reviewed.
- Guidelines for adding examples and docs pages.

## License
Expand Down
81 changes: 45 additions & 36 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ framework-agnostic keyboard navigation library for lists, grids and trees.

## Packages

Published packages live under `packages/`; the docs site and the benchmark,
which are never published, live under `apps/`.

| Package | Path | Description |
| -------------------------- | ------------------------------------ | --------------------------------------------------- |
| `@mixedrays/keyrove` | [packages/keyrove](packages/keyrove) | The library. Published to npm. |
| `@mixedrays/keyrove/docs` | [packages/docs](packages/docs) | Documentation site and landing page. Not published. |
| `@mixedrays/keyrove/bench` | [packages/bench](packages/bench) | Browser benchmark and its UI. Not published. |
| `@mixedrays/keyrove-docs` | [apps/docs](apps/docs) | Documentation site and landing page. Not published. |
| `@mixedrays/keyrove-bench` | [apps/bench](apps/bench) | Browser benchmark and its UI. Not published. |

## Getting started

Expand Down Expand Up @@ -39,15 +42,15 @@ Run from the repo root:
| `pnpm publish:npm` | Publishes the library to npm. See [Releasing](#releasing). |

The benchmark's method and recorded results are in
[packages/bench](packages/bench/README.md), with how to compare releases:
[apps/bench](apps/bench/README.md), with how to compare releases:
`pnpm bench:headless -- --ref v2.1.0 --ref v2.2.0` benchmarks each tag and
saves a report for it, and `pnpm bench` compares them side by side.

Any script can be aimed at one package with a filter:

```sh
pnpm --filter @mixedrays/keyrove test
pnpm --filter @mixedrays/keyrove/docs build
pnpm --filter @mixedrays/keyrove-docs build
```

## Browser tests
Expand Down Expand Up @@ -100,7 +103,7 @@ npm, it publishes nothing.
## How the docs site works

Every page is one markdown file under
[packages/docs/content](packages/docs/content), and its path is its URL:
[apps/docs/content](apps/docs/content), and its path is its URL:
`content/docs/examples/basic.md` is served at `/docs/examples/basic`. Adding a
page means adding a file — the sidebar, the "On this page" rail, the prev/next
pager, and `llms.txt` are all derived from what is on disk.
Expand Down Expand Up @@ -147,7 +150,7 @@ is a link, so a modified click still opens it in a new tab.
The content plugin generates the index in development and production.
Markdown edits invalidate the development cache and reload the page,
including search. Run the search indexing tests with
`pnpm --filter @mixedrays/keyrove/docs test`.
`pnpm --filter @mixedrays/keyrove-docs test`.

### Sidebar navigation

Expand All @@ -166,26 +169,8 @@ keys as well while keyboard focus is inside it.

### Live demos

A content file embeds a demo with `<div data-demo="grid"></div>`. The markup
behind it is one file under
[packages/docs/content/_demos](packages/docs/content/_demos), named after the
demo. [packages/docs/build/demos.ts](packages/docs/build/demos.ts) stamps it
into the page twice, once as live elements and once as the source block under
them, so the two cannot drift. Repeated items wrapped in `<!-- fold -->` …
`<!-- /fold -->` still run but collapse to one comment in the source block,
which keeps a 24-item list from costing 24 lines of a page that is also served
as markdown.

Layout the demo needs but the library does not teach — the grid's columns, the
long list's scroll box — goes in `data-demo-class` on the placeholder rather
than in the fragment, so what a reader copies is markup they can paste as-is.
[packages/docs/src/demos.ts](packages/docs/src/demos.ts) wires the behaviour
markup cannot carry: the keydown listener and the move log. React draws the
panel around a demo, with its code tabs and copy button, but leaves the live
preview as the fragment's own markup, which keyrove rearranges as keys are
pressed.

For a compact **demo panel** on any docs page, add a label:
A content file embeds a demo with a placeholder that names it and labels its
panel:

````md
<div data-demo="loop" data-demo-label="account menu"></div>
Expand All @@ -198,13 +183,37 @@ menu.addEventListener('keydown', (e) => keyRove(e));
```
````

A demo is two things, both named after it: its markup, one file under
[apps/docs/content/_demos](apps/docs/content/_demos), and its entry in
[apps/docs/build/demo-definitions.ts](apps/docs/build/demo-definitions.ts),
which declares what it adds to navigation — group options, extra handlers
such as typeahead, and decorative icons. A fragment without an entry, or an
entry without a fragment, fails the docs tests.

[apps/docs/build/demos.ts](apps/docs/build/demos.ts) stamps the
fragment into the page twice, once as live elements and once as the source
block under them, so the two cannot drift. Repeated items wrapped in
`<!-- fold -->` … `<!-- /fold -->` still run but collapse to one comment in the
source block, which keeps a 24-item list from costing 24 lines of a page that
is also served as markdown.

The panel includes a live preview, a single-line readout, and tabs for the
fragment's HTML and the adjacent code blocks. It uses the opposite theme to
the page and keeps long code blocks scrollable. Omit `data-demo-label` to use
the existing detailed demo presentation. `data-demo-class`, when needed,
goes before `data-demo-label`.
the page and keeps long code blocks scrollable. Layout the demo needs but the
library does not teach — the grid's columns, the long list's scroll box — goes
in `data-demo-class` on the placeholder rather than in the fragment, so what a
reader copies is markup they can paste as-is. The attributes go in any order;
a placeholder the build cannot read fails it.

[apps/docs/src/demos.ts](apps/docs/src/demos.ts) wires the behaviour
markup cannot carry: the keydown listener and the extras the entry lists.
[apps/docs/src/readout.ts](apps/docs/src/readout.ts) writes the
readout, for the panels and the landing page's hero alike. React draws the
panel around a demo, with its code tabs and copy button, but leaves the live
preview as the fragment's own markup, which keyrove rearranges as keys are
pressed.

[demo-panel.css](packages/docs/src/demo-panel.css) owns the shared presentation;
[demo-panel.css](apps/docs/src/demo-panel.css) owns the shared presentation;
the embedding page owns column widths and spacing. No hero or landing classes
are required. Code-only examples can use a
`<div class="demo-panel demo-panel--code">` around adjacent fenced blocks,
Expand All @@ -215,12 +224,12 @@ with blank lines between the wrapper and the fences.
The site is a [TanStack Start](https://tanstack.com/start) app, and its UI
components are [shadcn/ui](https://ui.shadcn.com) on
[Base UI](https://base-ui.com) primitives, in
[packages/docs/src/components/ui](packages/docs/src/components/ui). They keep
[apps/docs/src/components/ui](apps/docs/src/components/ui). They keep
shadcn's API but not its styles: each passes the class style.css already draws
that part of the site with, since a Tailwind utility on the component would
outrank every rule in the stylesheet's components layer. `components.json`
is set up for the shadcn CLI, so `pnpm dlx shadcn@latest add <component>`
works from `packages/docs`.
works from `apps/docs`.

Keyboard navigation is split by who has it built in. Base UI owns it where its
component does — the code tabs and the landing page's pattern tabs, the search
Expand All @@ -230,12 +239,12 @@ the landing hero and every demo.

### Build

[packages/docs/vite-plugin-content.ts](packages/docs/vite-plugin-content.ts)
[apps/docs/vite-plugin-content.ts](apps/docs/vite-plugin-content.ts)
hands the content to the app as virtual modules: the sidebar and each page's
metadata in one, each page's rendered body in a chunk of its own, and the
generated files — the `.md` twins, `llms.txt`, the sitemap — in a third that
only the server routes under
[packages/docs/src/routes](packages/docs/src/routes) import. Markdown renders
[apps/docs/src/routes](apps/docs/src/routes) import. Markdown renders
through the same code in dev and in the build. A page's body reaches the
browser as a tree of elements built at build time rather than as HTML, so
there is no HTML parser in the bundle and nothing for hydration to disagree
Expand All @@ -257,15 +266,15 @@ relative asset paths, so links are absolute to `base` — set `DOCS_BASE` when
deploying to a subpath:

```sh
DOCS_BASE=/keyrove/ pnpm --filter @mixedrays/keyrove/docs build
DOCS_BASE=/keyrove/ pnpm --filter @mixedrays/keyrove-docs build
```

`DOCS_SITE_URL` sets the origin used for the links in `llms.txt`.

### Resolving the library

The docs site aliases `@mixedrays/keyrove` to the library's TypeScript source
(see [packages/docs/vite.config.ts](packages/docs/vite.config.ts)), so
(see [apps/docs/vite.config.ts](apps/docs/vite.config.ts)), so
`pnpm dev` hot-reloads library edits without a build step in between. The
published package still ships `dist` — nothing about its `exports` changes.

Expand Down
Loading
Loading