Skip to content

Commit 0b79563

Browse files
committed
A flat directory was never a way to read fifteen documents
The docs become a site on GitHub Pages, and a tree that follows the system's parts rather than a reading order: store, components, themes, platform, terminal, documents, cli, testing. Fifteen files become 53 pages, each section opening on one that says what the section is for. The prose is unchanged except where a split left a page starting cold, and every page still reads on github.com - the `.md` links stay, and `jekyll-relative-links` rewrites them at build time. Two things the build would not accept, both found by building it rather than by reading it. **Jekyll runs Liquid over the page body before markdown.** So `border={{ style: 'single' }}` in a fenced block is an unterminated Liquid variable, and the build dies on it - as does the store's own `$/rows/{{ $/active/id }}`, and a `fg={{ rgb: ... }}` sitting in inline code in the middle of a sentence. Seven of them across five files. `{% raw %}` would fix each one and litter the prose on github.com, which is the thing the whole arrangement exists to avoid, so `render_with_liquid: false` is a front-matter default instead. That then broke the theme, whose stylesheets are themselves *built* from Liquid, so `assets` opts back in on a narrower scope. **just-the-docs matches a child to its section by title string**, not by path. A `parent:` that matches nothing does not fail: the page silently leaves the sidebar and Jekyll reports success. Two collisions existed here by construction - the terminal adapter and the resource adapter, which the vocabulary already warns never meet - and they are named apart now. `scripts/check-docs.mjs` fails on a parent that resolves to nothing, a duplicate title, a dead link, and an H1 that disagrees with the title it is filed under. It runs in CI, and it needs no Ruby. Nor does anything else here. `scripts/docs-serve.sh` runs jekyll in a container; `scripts/docs-preview.py` serves an already-built `_site` with nothing but the standard library. That one exists because the site is built with `baseurl: /textui`, so every link in it is absolute at `/textui/...` and a plain `python -m http.server` over `_site` misses every file it asks for - which reads as a broken site rather than as a prefix nobody mounted. It takes `--host 0.0.0.0` when the docs need to be read from somewhere else, and binds loopback when they do not. `_config.yml`, the Gemfile and the workflow went in with ad13792 alongside work on the catalog; this is the rest of the same change.
1 parent 4c9016c commit 0b79563

60 files changed

Lines changed: 2350 additions & 21 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
cache: pnpm
1919

2020
- run: pnpm install --frozen-lockfile
21+
- run: node scripts/check-docs.mjs
2122
- run: pnpm build
2223
- run: pnpm typecheck
2324
- run: pnpm lint

.github/workflows/docs.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ jobs:
2929
steps:
3030
- uses: actions/checkout@v4
3131

32+
# Cheap, and it catches the one failure Jekyll reports as success: a
33+
# `parent:` that matches no title, which drops the page from the sidebar.
34+
- uses: actions/setup-node@v4
35+
with:
36+
node-version: 22
37+
- name: Check the docs tree
38+
run: node scripts/check-docs.mjs
39+
3240
- uses: ruby/setup-ruby@v1
3341
with:
3442
ruby-version: '3.3'

CLAUDE.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@
22

33
TextUI is a terminal UI runtime. A screen is data - a graph of component nodes resolved through late-binding registries - and JSX is a way of writing that data, not a different thing from it.
44

5-
Read [`README.md`](README.md), then [`docs/`](docs/README.md): the architecture, the vocabulary, and the rules a consumer has to follow.
5+
Read [`README.md`](README.md), then [`docs/`](docs/index.md): the architecture, the vocabulary, and the rules a consumer has to follow.
6+
7+
The docs are a Jekyll site published to GitHub Pages. There is no Ruby on this
8+
machine and there is not going to be - `scripts/docs-serve.sh` runs it in a
9+
container, `scripts/docs-preview.py` serves an already-built `_site` with no
10+
container at all, and `node scripts/check-docs.mjs` checks the nav tree without
11+
either. A plain `python -m http.server` over `docs/_site` will 404 on every
12+
file, because the site is built with `baseurl: /textui` and nothing is mounted
13+
there.
14+
Two rules the build enforces: a page's `parent:` must match another page's
15+
`title:` exactly, and `render_with_liquid: false` is what stops Jekyll reading
16+
`{{` in a JSX prop as a Liquid variable.
617

718
## Commands
819

README.md

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,30 @@ Everything else follows from that: one reactive store addressed by paths, typed
3636

3737
## Documentation
3838

39+
Published at **<https://softov.github.io/textui/>**, and readable in
40+
[`docs/`](docs/index.md) as plain markdown.
41+
42+
Start here:
43+
3944
| Document | What it answers |
4045
| --- | --- |
41-
| [`docs/README.md`](docs/README.md) | The vocabulary everything else assumes |
42-
| [`docs/architecture.md`](docs/architecture.md) | The model: store, graph, registries, surfaces, shells |
43-
| [`docs/getting-started.md`](docs/getting-started.md) | From nothing to a running application |
44-
| [`docs/components.md`](docs/components.md) | The catalog, and how to write one |
45-
| [`docs/theming.md`](docs/theming.md) | Tokens, glyphs, borders, density, capability downgrade |
46-
| [`docs/store.md`](docs/store.md) | Paths, scopes, computed, collections, providers, events |
47-
| [`docs/commands-focus.md`](docs/commands-focus.md) | Commands, keybindings, focus scopes, layers |
48-
| [`docs/resources.md`](docs/resources.md) | Kinds, providers, viewers, editors, actions, adapters, documents |
49-
| [`docs/syntax.md`](docs/syntax.md) | Highlighters, scopes, and how a theme colours them |
50-
| [`docs/adapters.md`](docs/adapters.md) | Terminals, capabilities, managed and embedded sessions |
51-
| [`docs/testing.md`](docs/testing.md) | The harness, and what to assert |
52-
| [`docs/cli.md`](docs/cli.md) | The developer CLI and the registry model |
53-
| [`docs/templates.md`](docs/templates.md) | The shipped templates |
54-
| [`docs/extending.md`](docs/extending.md) | Registries, manifests, extension points |
55-
| [`docs/decisions.md`](docs/decisions.md) | What was chosen, and what it cost |
46+
| [Getting started](docs/getting-started.md) | From nothing to a running application |
47+
| [The vocabulary](docs/vocabulary.md) | The words everything else assumes |
48+
| [Architecture](docs/architecture.md) | The model: store, graph, registries, surfaces, shells |
49+
| [Decisions and tradeoffs](docs/decisions.md) | What was chosen, and what it cost |
50+
51+
Then by subsystem:
52+
53+
| Section | What it covers |
54+
| --- | --- |
55+
| [Store](docs/store/index.md) | Paths, scopes, computed, collections, providers, events |
56+
| [Components](docs/components/index.md) | The catalog, how to write one, and the templates |
57+
| [Themes](docs/themes/index.md) | Tokens, glyphs, borders, capability downgrade, syntax |
58+
| [Platform](docs/platform/index.md) | Commands, keybindings, focus, layers, screens, extension points |
59+
| [Terminal](docs/terminal/index.md) | Adapters, capabilities, managed and embedded sessions |
60+
| [Documents](docs/documents/index.md) | Resource kinds, providers, viewers, editors, buffers |
61+
| [CLI](docs/cli/index.md) | The developer CLI and the registry model |
62+
| [Testing](docs/testing/index.md) | The harness, and what to assert |
5663

5764
## Development
5865

@@ -65,6 +72,22 @@ pnpm dev --list # the playgrounds
6572
pnpm dev gallery # open one
6673
```
6774

75+
The docs site is Jekyll, and needs no Ruby on your machine - it builds in a
76+
container:
77+
78+
```bash
79+
scripts/docs-serve.sh # live, with reload, at localhost:4000/textui/
80+
scripts/docs-serve.sh --build # build once, into docs/_site
81+
scripts/docs-preview.py # serve what was built, at localhost:8000/textui/
82+
scripts/docs-preview.py --host 0.0.0.0 # ...and reachable from the network
83+
node scripts/check-docs.mjs # the nav tree, links and titles
84+
```
85+
86+
`docs-preview.py` exists because the site is built with `baseurl: /textui`, so
87+
every link in it is absolute at `/textui/...`. A plain `python -m http.server`
88+
over `docs/_site` 404s on all of it; this one mounts the site under the prefix
89+
the pages actually ask for.
90+
6891
Node ≥ 22, pnpm 10.
6992

7093
## The acceptance test

docs/Gemfile.lock

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
addressable (2.9.0)
5+
public_suffix (>= 2.0.2, < 8.0)
6+
base64 (0.3.0)
7+
bigdecimal (4.1.2)
8+
colorator (1.1.0)
9+
concurrent-ruby (1.3.8)
10+
csv (3.3.6)
11+
em-websocket (0.5.3)
12+
eventmachine (>= 0.12.9)
13+
http_parser.rb (~> 0)
14+
eventmachine (1.2.7)
15+
ffi (1.17.4)
16+
ffi (1.17.4-aarch64-linux-gnu)
17+
ffi (1.17.4-aarch64-linux-musl)
18+
ffi (1.17.4-arm-linux-gnu)
19+
ffi (1.17.4-arm-linux-musl)
20+
ffi (1.17.4-arm64-darwin)
21+
ffi (1.17.4-x86-linux-gnu)
22+
ffi (1.17.4-x86-linux-musl)
23+
ffi (1.17.4-x86_64-darwin)
24+
ffi (1.17.4-x86_64-linux-gnu)
25+
ffi (1.17.4-x86_64-linux-musl)
26+
forwardable-extended (2.6.0)
27+
google-protobuf (4.36.0)
28+
bigdecimal
29+
rake (~> 13.3)
30+
google-protobuf (4.36.0-aarch64-linux-gnu)
31+
bigdecimal
32+
rake (~> 13.3)
33+
google-protobuf (4.36.0-aarch64-linux-musl)
34+
bigdecimal
35+
rake (~> 13.3)
36+
google-protobuf (4.36.0-arm64-darwin)
37+
bigdecimal
38+
rake (~> 13.3)
39+
google-protobuf (4.36.0-x86-linux-gnu)
40+
bigdecimal
41+
rake (~> 13.3)
42+
google-protobuf (4.36.0-x86-linux-musl)
43+
bigdecimal
44+
rake (~> 13.3)
45+
google-protobuf (4.36.0-x86_64-darwin)
46+
bigdecimal
47+
rake (~> 13.3)
48+
google-protobuf (4.36.0-x86_64-linux-gnu)
49+
bigdecimal
50+
rake (~> 13.3)
51+
google-protobuf (4.36.0-x86_64-linux-musl)
52+
bigdecimal
53+
rake (~> 13.3)
54+
http_parser.rb (0.8.1)
55+
i18n (1.15.2)
56+
concurrent-ruby (~> 1.0)
57+
jekyll (4.4.1)
58+
addressable (~> 2.4)
59+
base64 (~> 0.2)
60+
colorator (~> 1.0)
61+
csv (~> 3.0)
62+
em-websocket (~> 0.5)
63+
i18n (~> 1.0)
64+
jekyll-sass-converter (>= 2.0, < 4.0)
65+
jekyll-watch (~> 2.0)
66+
json (~> 2.6)
67+
kramdown (~> 2.3, >= 2.3.1)
68+
kramdown-parser-gfm (~> 1.0)
69+
liquid (~> 4.0)
70+
mercenary (~> 0.3, >= 0.3.6)
71+
pathutil (~> 0.9)
72+
rouge (>= 3.0, < 5.0)
73+
safe_yaml (~> 1.0)
74+
terminal-table (>= 1.8, < 4.0)
75+
webrick (~> 1.7)
76+
jekyll-include-cache (0.2.2)
77+
jekyll (>= 3.7, < 5.0)
78+
jekyll-relative-links (0.8.0)
79+
jekyll (>= 3.3, < 5.0)
80+
jekyll-sass-converter (3.1.0)
81+
sass-embedded (~> 1.75)
82+
jekyll-seo-tag (2.9.0)
83+
jekyll (>= 3.8, < 5.0)
84+
jekyll-watch (2.2.1)
85+
listen (~> 3.0)
86+
json (2.21.2)
87+
just-the-docs (0.12.0)
88+
jekyll (>= 3.8.5)
89+
jekyll-include-cache
90+
jekyll-seo-tag (>= 2.0)
91+
rake (>= 12.3.1)
92+
kramdown (2.5.2)
93+
rexml (>= 3.4.4)
94+
kramdown-parser-gfm (1.1.0)
95+
kramdown (~> 2.0)
96+
liquid (4.0.4)
97+
listen (3.10.0)
98+
logger
99+
rb-fsevent (~> 0.10, >= 0.10.3)
100+
rb-inotify (~> 0.9, >= 0.9.10)
101+
logger (1.7.0)
102+
mercenary (0.4.0)
103+
pathutil (0.16.2)
104+
forwardable-extended (~> 2.6)
105+
public_suffix (7.0.5)
106+
rake (13.4.2)
107+
rb-fsevent (0.11.2)
108+
rb-inotify (0.11.1)
109+
ffi (~> 1.0)
110+
rexml (3.4.4)
111+
rouge (4.7.0)
112+
safe_yaml (1.0.5)
113+
sass-embedded (1.103.1)
114+
google-protobuf (~> 4.31)
115+
rake (>= 13)
116+
sass-embedded (1.103.1-aarch64-linux-android)
117+
google-protobuf (~> 4.31)
118+
sass-embedded (1.103.1-aarch64-linux-gnu)
119+
google-protobuf (~> 4.31)
120+
sass-embedded (1.103.1-aarch64-linux-musl)
121+
google-protobuf (~> 4.31)
122+
sass-embedded (1.103.1-arm-linux-androideabi)
123+
google-protobuf (~> 4.31)
124+
sass-embedded (1.103.1-arm-linux-gnueabihf)
125+
google-protobuf (~> 4.31)
126+
sass-embedded (1.103.1-arm-linux-musleabihf)
127+
google-protobuf (~> 4.31)
128+
sass-embedded (1.103.1-arm64-darwin)
129+
google-protobuf (~> 4.31)
130+
sass-embedded (1.103.1-riscv64-linux-android)
131+
google-protobuf (~> 4.31)
132+
sass-embedded (1.103.1-riscv64-linux-gnu)
133+
google-protobuf (~> 4.31)
134+
sass-embedded (1.103.1-riscv64-linux-musl)
135+
google-protobuf (~> 4.31)
136+
sass-embedded (1.103.1-x86_64-darwin)
137+
google-protobuf (~> 4.31)
138+
sass-embedded (1.103.1-x86_64-linux-android)
139+
google-protobuf (~> 4.31)
140+
sass-embedded (1.103.1-x86_64-linux-gnu)
141+
google-protobuf (~> 4.31)
142+
sass-embedded (1.103.1-x86_64-linux-musl)
143+
google-protobuf (~> 4.31)
144+
terminal-table (3.0.2)
145+
unicode-display_width (>= 1.1.1, < 3)
146+
unicode-display_width (2.6.0)
147+
webrick (1.9.2)
148+
149+
PLATFORMS
150+
aarch64-linux-android
151+
aarch64-linux-gnu
152+
aarch64-linux-musl
153+
arm-linux-androideabi
154+
arm-linux-gnu
155+
arm-linux-gnueabihf
156+
arm-linux-musl
157+
arm-linux-musleabihf
158+
arm64-darwin
159+
riscv64-linux-android
160+
riscv64-linux-gnu
161+
riscv64-linux-musl
162+
ruby
163+
x86-linux-gnu
164+
x86-linux-musl
165+
x86_64-darwin
166+
x86_64-linux-android
167+
x86_64-linux-gnu
168+
x86_64-linux-musl
169+
170+
DEPENDENCIES
171+
base64
172+
bigdecimal
173+
csv
174+
jekyll (~> 4.4)
175+
jekyll-include-cache (~> 0.2)
176+
jekyll-relative-links (~> 0.7)
177+
jekyll-seo-tag (~> 2.8)
178+
just-the-docs (~> 0.12)
179+
180+
BUNDLED WITH
181+
2.5.22

docs/_config.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ defaults:
8383
path: ""
8484
values:
8585
layout: default
86+
# Jekyll runs Liquid over the page body before markdown, so a JSX prop
87+
# like `border={{ style: 'single' }}` or the store's `$/rows/{{ $/id }}`
88+
# is read as an unterminated Liquid variable and fails the build. No page
89+
# here uses Liquid in its body; the layouts and includes still do.
90+
render_with_liquid: false
91+
92+
# The theme's stylesheets are *built* from Liquid (`{% include ... %}`), so
93+
# the rule above must not reach them. A more specific scope wins.
94+
- scope:
95+
path: "assets"
96+
values:
97+
render_with_liquid: true
8698

8799
exclude:
88100
- Gemfile

docs/cli/commands.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: CLI commands
3+
parent: CLI
4+
nav_order: 1
5+
---
6+
7+
# CLI commands
8+
9+
| Command | What it does |
10+
| --- | --- |
11+
| `init` | Write `textui.config.json` and create the component directories |
12+
| `add <names...>` | Copy components and their dependencies |
13+
| `create <template>` | Scaffold a template, pulling in the components it composes |
14+
| `list` | Registry components, templates and themes; `--catalog` for the built-ins |
15+
| `theme [name]` | List themes, preview one, or `--set` the project default |
16+
| `registry add <name> <path>` | Register another registry |
17+
| `diff` | What has drifted from upstream |
18+
| `doctor` | What this terminal can do, and what the project looks like |
19+
20+
Useful flags: `--dry-run` on `add` shows what would be written and writes
21+
nothing; `--json` on `list` and `doctor` gives machine-readable output.

docs/cli/configuration.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: Configuration
3+
parent: CLI
4+
nav_order: 2
5+
---
6+
7+
# Configuration
8+
9+
```jsonc
10+
// textui.config.json
11+
{
12+
"componentsDir": "src/ui",
13+
"templatesDir": "src/screens",
14+
"alias": "@textui/core", // rewritten into every copied file
15+
"theme": "workbench",
16+
"shell": "workbench",
17+
"registries": { "internal": "../design-system/registry" }
18+
}
19+
```
20+
21+
The `alias` is what makes the copy fit your project: a file copied into a repo
22+
that imports the runtime as `~/textui` gets that import, not `@textui/core`.

docs/cli/index.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
title: CLI
3+
nav_order: 11
4+
has_children: true
5+
---
6+
7+
# CLI
8+
9+
```bash
10+
npx textui init
11+
npx textui add service-table
12+
npx textui create dashboard
13+
npx textui doctor
14+
```
15+
16+
## The source-copy model
17+
18+
`textui add` copies a component's **source** into your project rather than
19+
adding an import. You own it, edit it, and it is reviewed with the rest of your
20+
code.
21+
22+
What makes that survivable is the receipt written to `.textui/components.json`:
23+
origin, version and a content hash per file. The CLI can then tell "you have not
24+
touched this" from "you changed it", and never overwrites the second kind
25+
without `--force`.
26+
27+
```
28+
$ textui add status-dot
29+
! src/ui/status-dot.tsx - you have edited this; left alone (use --force to replace)
30+
```
31+
32+
`textui diff` shows which way things have drifted:
33+
34+
```
35+
M status-dot src/ui/status-dot.tsx modified # you edited it
36+
U service-table src/ui/service-table.tsx outdated # upstream changed
37+
! metric-card src/ui/metric-card.tsx missing # the file is gone
38+
```

0 commit comments

Comments
 (0)