tosijs-ui 1.16.20 → 1.16.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +2 -2
  3. package/bin/make-icon-data.js +1 -1
  4. package/dist/code-editor.js +2 -3
  5. package/dist/data-table.js +3 -16
  6. package/dist/doc-browser.d.ts +4 -0
  7. package/dist/doc-browser.js +46 -101
  8. package/dist/doc-system/doc-system-styles.js +11 -0
  9. package/dist/doc-system/doc-system.js +1 -1
  10. package/dist/doc-system/highlight-styles.js +11 -16
  11. package/dist/doc-system/routing.d.ts +9 -0
  12. package/dist/doc-system/routing.js +22 -6
  13. package/dist/doc-system/site/build-warnings.d.ts +10 -0
  14. package/dist/doc-system/site/build-warnings.js +24 -0
  15. package/dist/doc-system/site/dev-auth.d.ts +13 -0
  16. package/dist/doc-system/site/dev-auth.js +40 -2
  17. package/dist/doc-system/site/dev-server.js +13 -1
  18. package/dist/doc-system/site/docs.d.ts +14 -0
  19. package/dist/doc-system/site/docs.js +61 -81
  20. package/dist/doc-system/site/epub.js +6 -2
  21. package/dist/doc-system/site/generate-site.js +15 -5
  22. package/dist/doc-system/site/orchestrator.js +1 -0
  23. package/dist/doc-system/site/site-config.d.ts +25 -0
  24. package/dist/editable-rect.js +1 -3
  25. package/dist/highlight-block.js +3 -10
  26. package/dist/icon-rules.d.ts +10 -0
  27. package/dist/icon-rules.js +13 -5
  28. package/dist/icon-svg.js +13 -19
  29. package/dist/icons.js +20 -17
  30. package/dist/iife.js +102 -106
  31. package/dist/iife.js.map +24 -24
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +1 -0
  34. package/dist/live-example/component.js +3 -4
  35. package/dist/menu.js +2 -2
  36. package/dist/natural-compare.js +6 -2
  37. package/dist/password-strength.js +1 -1
  38. package/dist/pocket-bar.js +2 -2
  39. package/dist/size-break.js +4 -3
  40. package/dist/track-drag.js +1 -1
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.js +1 -1
  43. package/llms.txt +12 -8
  44. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,89 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.16.22
4
+
5
+ Three site options asked for by adopters, and a review pass over the documentation.
6
+
7
+ - **`home`: choose the page served at `/`.** `defineSiteConfig({ home: 'host' })` (a slug or
8
+ filename) makes that page the site root, for a site that is an app. README then has its own
9
+ page at `/readme/`, and the nav, canonical links, sitemap, `llms.txt` and books follow. A
10
+ doc can claim the root itself with `{"home": true}` (or `home: true` in YAML frontmatter).
11
+ The build fails when `home` names no page or a hidden one, or when two docs claim the root.
12
+ **README's URL and canonical link change** when you turn it on; Doc-Site System says so. An
13
+ ePub takes its slugs from the whole site, so README keeps `/readme/` in a book even when the
14
+ home doc is not in that book. closes #225
15
+ - **`{"header": false}`: a page without the site header.** It removes the header bar on that
16
+ page, before hydration and after, and gives its space to the page. With
17
+ `layout: "full-screen"` the page has no site chrome at all, for an app page with its own
18
+ toolbar or an installed PWA. The page links back to the docs its own way. closes #226
19
+ - **YAML frontmatter reads `header: false` and `home: true`**, as the JSON metadata block does.
20
+ - **Doc-Site System documents `header: false` and `home`**, including that home moves
21
+ README's URL.
22
+ - **The build warns (fails under `strict`) on a link written as a bare page name.**
23
+ `[valueRenderer](value-renderer)` resolves under the page it is on and reaches nothing;
24
+ three shipped that way here.
25
+ - **`naturalCompare`, `naturalSorter` and `isBlank` are exported from the package root.**
26
+ The naturalCompare page imported `naturalCompare` from `'tosijs-ui'`, got `undefined`, and
27
+ showed the lexical sort it warns against.
28
+ - **The documentation site is regrouped.** A new Documentation System section holds every
29
+ page about the doc system (adopting, One Source, Doc-Site System, doc-system, doc-browser,
30
+ docs, highlight-block, highlight-styles, the layout demos, kitchen sink). Icons moves under
31
+ Components, with Icon Composition, iconSvg and make-icon-data beneath it; naturalCompare
32
+ joins Helper Libraries; Appendices keeps migrating and release-notes.
33
+ - **Documentation fixes.**
34
+ - The doc-browser and docs pages showed a CLI that does not exist, imports from the wrong
35
+ module and wrong option names. Both are rewritten against the real API.
36
+ - Three links (data-table → value-renderer, pocket-bar → icon-composition and popFloat)
37
+ reached nothing.
38
+ - Migrating: the ACE-era `theme`/`options` props warn and do nothing, rather than being
39
+ gone; the 1.11.0 edit-link note points to the 1.14.0 change; new notices for 1.16.17
40
+ (tjs-lang 0.14), 1.16.19 (`iconSvg` throws) and 1.16.21 (tosijs `^1.10.9`).
41
+ - Adopting and Doc-Site System: install `chokidar` with the build peers; display-only
42
+ fences use `:static`; a detached test block shows its results.
43
+ - Icons: the class added is `tosi-icon` (no `icon-chevron-down`); how icons are coloured
44
+ replaces the old icomoon paragraph.
45
+ - Live examples: `ide` mode is described as what it is today (it runs like `iframe`).
46
+ - Smaller: size-break's container-query intro, the iife size (~484KB gzip), code-editor's
47
+ mode list (`ajs`), a stray CSS line in editable-rect's example, the menu demo's link, and
48
+ typos across six pages.
49
+ - `dist/iife.js` grows by about 0.2KB gzip.
50
+
51
+ ### Dev-server access: `trustLan` (opt-in)
52
+
53
+ Who may read and write source through the dev server changed, but only for a project that
54
+ sets the new option; with it unset the rules are exactly 1.16.21's.
55
+
56
+ - **`trustLan`: LAN devices may read and edit source.** With `editableSources` on, a direct
57
+ peer at a private address (RFC 1918, IPv6 unique-local or link-local) may read and save
58
+ source as one at the dev machine can, so a phone or headset on the LAN uses "Edit page
59
+ source" without the tunnel (a live example's "Save to source" is still offered only on
60
+ `localhost`). closes #227
61
+ - **What is never trusted:** tunnel traffic (it still needs a session), a request with
62
+ forwarding headers, and a write from any origin but the dev server's own. Agent driving,
63
+ invite links, builds and test reports stay this machine only; agent driving no longer
64
+ inherits the write rule's options, so LAN trust cannot widen it.
65
+ - **Anyone on the network can then edit your working tree**, which includes code that runs on
66
+ this machine: use it on a network you control. The dev server prints a warning at every
67
+ launch while it is on, and says so when it is set without `editableSources` (it then does
68
+ nothing).
69
+ - The edit and save alerts tell a LAN reader that invite links cannot help them, instead of
70
+ sending them round that loop.
71
+
72
+
73
+ ## 1.16.21
74
+
75
+ - **Expanding or collapsing a table row's detail no longer corrects `scrollTop` itself.**
76
+ tosijs 1.10.9's variable-height list keeps the row in place across the height change, so
77
+ `<tosi-table>` stopped measuring the row and nudging the scroll position up to three
78
+ times. closes board #3326
79
+ - **tosijs peer floor raised to `^1.10.9`**, the release that anchors the list. On an older
80
+ tosijs the toggled row now jumps when its detail opens or closes; upgrade tosijs alongside
81
+ (`dist/iife.js` already bundles 1.10.9).
82
+ - **The icons page points to [lucide](https://lucide.dev)** in its introduction and under
83
+ Icon Sources: a fork and expansion of Feather, and the place to start for more icons in
84
+ the same style.
85
+
86
+
3
87
  ## 1.16.20
4
88
 
5
89
  - **A registered icon with style suffixes wins over a composition rule.** `checkCircle90r`
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  ></tosi-lottie>
15
15
  </center>
16
16
 
17
- Copyright ©2023-2025 Tonio Loewald
17
+ Copyright ©2023-2026 Tonio Loewald
18
18
 
19
19
  > **Upgrading from an older version?** `<tosi-code>` changed editors in 1.7.0, and
20
20
  > 1.3.0 renamed everything from `xinjs-ui`. Both are covered in
@@ -53,7 +53,7 @@ The `tosijs-ui` iife build bundles `tosijs`, `tosijs-ui`, `marked` and — as of
53
53
  **CodeMirror** into a single minified javascript source file. You can access `xinjs` and
54
54
  `xinjsui` as globals which contain all the things exported by `tosijs` and `tosijs-ui`.
55
55
 
56
- > **Size (1.15):** ~468KB gzip, up from ~120KB in 1.6. Two things dominate it, and
56
+ > **Size (1.16.21):** ~484KB gzip, up from ~120KB in 1.6. Two things dominate it, and
57
57
  > `bun build --format iife` cannot
58
58
  > code-split, so `<tosi-code>`'s lazy CodeMirror import is flattened into this bundle
59
59
  > whether or not the page uses an editor. **If you don't need `<tosi-code>`, use the ESM
@@ -86,7 +86,7 @@ byte-identical files, so it never shows up as spurious diff noise.
86
86
  > your prettier config, which it now honours) — not the output.
87
87
  */
88
88
 
89
- /*{ "parent": "Appendices" }*/
89
+ /*{ "parent": "icons", "order": 30 }*/
90
90
 
91
91
  import fs from 'fs'
92
92
  import path from 'path'
@@ -6,7 +6,7 @@ A [CodeMirror 6](https://codemirror.net/) wrapper.
6
6
  Sometimes, it's nice to be able to just toss a code-editor in a web-page.
7
7
 
8
8
  `<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the
9
- language (`javascript`, `typescript`, `tjs`, `css`, `html`, `markdown`).
9
+ language (`javascript`, `typescript`, `tjs`, `ajs`, `css`, `html`, `markdown`).
10
10
 
11
11
  ```html
12
12
  <tosi-code style="width: 100%; height: 100%" mode="css">
@@ -69,8 +69,7 @@ CodeMirror is a **lazy chunk**: with a bundler (ESM), a page that never uses
69
69
  `<tosi-code>` doesn't load it. **This is not true of the IIFE** (`dist/iife.js`) —
70
70
  bun's IIFE format cannot code-split, so CodeMirror is inlined there. That is a
71
71
  deliberate trade: the doc-system's editor (and its save-to-source flow) is the
72
- point of the IIFE, so it carries the editor. It costs ~376KB gzipped, up from
73
- ~118KB in 1.6.x.
72
+ point of the IIFE, so it carries the editor; see the README for what the IIFE weighs.
74
73
 
75
74
  ## Migrating from the ACE editor (pre-1.7)
76
75
 
@@ -205,7 +205,7 @@ export interface ColumnOptions {
205
205
  ## Column value types
206
206
 
207
207
  Give a column a `type` and it's formatted (and aligned) automatically — no hand-rolled
208
- `dataCell`. The `type` is a [`valueRenderer`](value-renderer) string:
208
+ `dataCell`. The `type` is a [`valueRenderer`](/value-renderer/) string:
209
209
 
210
210
  ```
211
211
  [
@@ -1770,24 +1770,11 @@ export class TosiTable extends withAttributes({
1770
1770
  this._detailEls.delete(key);
1771
1771
  }
1772
1772
  const rowEl = this.stampedRow(raw);
1773
- const top = rowEl?.getBoundingClientRect().top;
1774
1773
  if (rowEl)
1775
1774
  this.syncDetail(rowEl, raw);
1775
+ // The list anchors itself across a height change (tosijs 1.10.9), so the toggled row
1776
+ // keeps its place without a scrollTop correction here.
1776
1777
  this.remeasureRows();
1777
- /*
1778
- Keep the toggled row where it was. A variable-height list places its rows by
1779
- interpolation, so a row that changes height moves every row, itself included, by an
1780
- amount that depends on how far down the list is scrolled.
1781
- */
1782
- if (top !== undefined && this._scrollArea) {
1783
- for (let i = 0; i < 3; i += 1) {
1784
- const now = this.stampedRow(raw)?.getBoundingClientRect().top;
1785
- if (now === undefined || Math.abs(now - top) < 1)
1786
- break;
1787
- this._scrollArea.scrollTop += now - top;
1788
- this.remeasureRows();
1789
- }
1790
- }
1791
1778
  this.dispatchEvent(new CustomEvent(want ? 'rowexpand' : 'rowcollapse', {
1792
1779
  bubbles: true,
1793
1780
  detail: { row: raw, element: element?.firstElementChild ?? null },
@@ -38,6 +38,10 @@ export interface Doc {
38
38
  path: string;
39
39
  pin?: string;
40
40
  hidden?: boolean;
41
+ /** this doc is the site root instead of README (the site's `home` option) */
42
+ home?: boolean;
43
+ /** `false` drops the site header on this page (see the site `Doc.header`) */
44
+ header?: boolean;
41
45
  /** `'full-width'` drops the reading measure; `'full-screen'` also puts the nav away. */
42
46
  layout?: 'full-width' | 'full-screen';
43
47
  testStatus?: 'passed' | 'failed' | 'pending';
@@ -1,26 +1,34 @@
1
1
  /*#
2
2
  # doc-browser
3
3
 
4
- The `tosijs-ui` library provides everything you need to create a self-documented testbed similar
5
- to the [tosijs-ui documentation site](https://ui.tosijs.net). It's like Storybook but much simpler
6
- to set up and maintain.
4
+ `createDocBrowser()` renders a documentation corpus as a browsable, searchable testbed with
5
+ live examples, like the [tosijs-ui documentation site](https://ui.tosijs.net). It's like
6
+ Storybook but much simpler to set up and maintain.
7
+
8
+ For a whole documentation site (static, pre-rendered pages, SEO, ePub, a dev server), use
9
+ [`tosijs-ui/site`](/adopting-the-doc-system/) instead; it builds on this component. Reach for
10
+ `createDocBrowser` directly to embed a doc browser in an app of your own.
7
11
 
8
12
  ## Quick Start
9
13
 
10
- ### 1. Extract Documentation
14
+ ### 1. Extract the documentation
11
15
 
12
- Use the CLI tool to extract documentation from your source files:
16
+ Run once at build time (it reads files, so it runs under bun or node, not in the browser):
13
17
 
14
- ```bash
15
- npx tosijs-ui-docs --dirs src,README.md --output docs.json
16
- ```
18
+ ```typescript
19
+ import { extractDocs } from 'tosijs-ui/site'
17
20
 
18
- This scans for:
21
+ extractDocs({
22
+ paths: ['src', 'README.md'],
23
+ output: 'docs.json',
24
+ })
25
+ ```
19
26
 
20
- - `.md` files (uses entire content)
21
- - Multi-line comments starting with `/*#` in `.ts`, `.js`, `.css` files
27
+ This collects every `.md` file and every `/*#` comment block in your sources. The format,
28
+ metadata and which code fences run are described under
29
+ [Doc format](/doc-site-system/#doc-format).
22
30
 
23
- ### 2. Create Your Doc Browser
31
+ ### 2. Create your doc browser
24
32
 
25
33
  ```typescript
26
34
  import { createDocBrowser } from 'tosijs-ui/doc-browser'
@@ -40,20 +48,16 @@ const browser = createDocBrowser({
40
48
  document.body.append(browser)
41
49
  ```
42
50
 
43
- ### 3. Add Live Examples in Your Docs
51
+ ### 3. Add live examples to your docs
44
52
 
45
- In your source files or markdown, use code fences. Any sequence of
46
- html, js, and css code examples will be turned in to a live, interactive
47
- example.
53
+ Consecutive `html`, `css`, `js` (or `ts`, `tjs`) and `test` code fences become one live,
54
+ editable example. `context` supplies the modules those examples import:
48
55
 
49
56
  /*#
50
57
  # My Component
51
58
 
52
59
  This component does amazing things!
53
60
 
54
- ```html
55
- <my-component></my-component>
56
- ```
57
61
  ```js
58
62
  import { myComponent } from 'mylib'
59
63
  preview.append(myComponent({ value: 'Hello!' }))
@@ -65,94 +69,33 @@ example.
65
69
  ```
66
70
  *‎/
67
71
 
68
- export class MyComponent extends Component {
69
- // ...
70
- }
71
-
72
- export const myComponent = MyComponent.elementCreator({
73
- tag: 'my-component'
74
- })
75
-
76
- ## Documentation Format
77
-
78
- ### Inline Comments
79
-
80
- Start multi-line comments with `/*#` to mark them as documentation:
81
-
82
- ```typescript
83
- /*#
84
- # Component Name
85
-
86
- Description and examples go here...
87
- *‎/
88
- ```
89
-
90
- ### Metadata
91
-
92
- Control sort order with JSON metadata:
93
-
94
- ```
95
- <!--{ "pin": "bottom" }-->
96
- ```
97
-
98
- or
99
-
100
- ```
101
- /*{ "pin": "bottom" }*‎/
102
- ```
103
-
104
- ## Programmatic API
105
-
106
- ```typescript
107
- import { extractDocs, saveDocsJSON } from 'tosijs-ui'
108
-
109
- const docs = extractDocs({
110
- dirs: ['src', 'README.md'],
111
- ignore: ['node_modules', 'dist'],
112
- })
113
-
114
- saveDocsJSON(docs, './docs.json')
115
-
116
- // Or use the docs directly
117
- import { createDocBrowser } from 'tosijs-ui/doc-browser'
118
- const browser = createDocBrowser({ docs, context: { mylib } })
119
- ```
120
-
121
72
  ## createDocBrowser Options
122
73
 
123
74
  ```typescript
124
75
  interface DocBrowserOptions {
125
- docs: Doc[] // Array of documentation objects
126
- context?: Record<string, any> // Modules for live examples
127
- projectName?: string // Display name
128
- projectLinks?: ProjectLinks // Links to show in header
129
- navSize?: number // Nav width (default: 200)
130
- minSize?: number // Min width before compact (default: 600)
131
- }
132
-
133
- interface ProjectLinks {
134
- github?: string
135
- npm?: string
136
- discord?: string
137
- blog?: string
138
- tosijs?: string
139
- bundle?: string
140
- cdn?: string
141
- [key: string]: string | undefined
76
+ docs: Doc[] // the extracted corpus
77
+ context?: Record<string, any> // modules live examples can import
78
+ projectName?: string // display name
79
+ projectLinks?: ProjectLinks // links in the header
80
+ logo?: string // header logo
81
+ navSize?: number // nav width (default: 200)
82
+ minSize?: number // width below which the nav goes compact (default: 600)
83
+ routing?: 'query' | 'path' | 'memory' // see below (default: 'query')
84
+ initialRoute?: string // memory routing: the first page
85
+ onRouteChange?: (slug: string) => void // memory routing: page changes
86
+ navbarLinks?: LinkItem[] // icon links in the header bar
87
+ contentElement?: HTMLElement // adopt this element as the content area
142
88
  }
143
89
  ```
144
90
 
145
- ## See Also
146
-
147
- The `tosijs-ui` demo is a complete working example. See:
148
-
149
- - `/tosijs-site.config.ts` - How this repo configures its doc site (`defineSiteConfig` / `tosijs-ui/site`)
150
- - `/src/doc-system/site/docs.ts` - The extraction tool (`extractDocs`, exported from `tosijs-ui/site`)
151
- - `/src/doc-browser.ts` - The createDocBrowser implementation
91
+ `routing` picks how pages map to URLs. `'query'` links pages as `?filename`. `'path'` uses
92
+ clean `/slug/` URLs, as the static site does. `'memory'` never touches the page's URL or
93
+ history, for a browser embedded in another page: drive it with `initialRoute`,
94
+ `onRouteChange` and the element's `navigate(slug)` method.
152
95
  */
153
- /*{"pin":"bottom","parent":"Appendices"}*/
96
+ /*{ "parent": "Documentation System", "order": 50 }*/
154
97
  import { elements, vars, varDefault, bindings, touch, getListItem, debounce, tosi, StyleSheet, } from 'tosijs';
155
- import { buildSlugMap, pathForSlug, filenameForPath, } from './doc-system/routing.js';
98
+ import { buildSlugMap, pathForSlug, filenameForPath, homeDoc, } from './doc-system/routing.js';
156
99
  import { buildNavTree } from './doc-system/nav-tree.js';
157
100
  import { renderDocMarkdown } from './doc-system/render.js';
158
101
  import { pageTitle } from './doc-system/doc-title.js';
@@ -476,7 +419,7 @@ export function createDocBrowser(options) {
476
419
  }
477
420
  return null;
478
421
  };
479
- const docName = filenameFromLocation() || docs[0]?.filename || 'README.md';
422
+ const docName = filenameFromLocation() || homeDoc(docs) || docs[0]?.filename || 'README.md';
480
423
  const currentDoc = docs.find((doc) => doc.filename === docName) || docs[0];
481
424
  // tosi() registers its top-level keys in a GLOBAL observable registry, so two
482
425
  // doc browsers on one page (e.g. a page whose live example embeds a nested
@@ -1053,6 +996,7 @@ export function createDocBrowser(options) {
1053
996
  layoutHost.setAttribute('data-layout', doc.layout);
1054
997
  else
1055
998
  layoutHost.removeAttribute('data-layout');
999
+ layoutHost.toggleAttribute('data-no-header', doc.header === false);
1056
1000
  }
1057
1001
  /*
1058
1002
  `full-screen` also collapses the nav, and that has to go through the COMPONENT rather than
@@ -1347,7 +1291,7 @@ export function createDocBrowser(options) {
1347
1291
  window.alert('This workspace needs an invite link before it can save.\n\n' +
1348
1292
  'Run `tosijs-tunnel --link` on the machine hosting it, open the link once, ' +
1349
1293
  'then try again. Your edit is still here.\n\n' +
1350
- 'If you opened this over the LAN (a `.local` address), saving will keep failing however many links you redeem — reach the workspace through its tunnel URL instead.');
1294
+ 'If you opened this over the LAN (a `.local` or local IP address), invite links will not help: set `trustLan: true` in the site config, or use its tunnel URL instead.');
1351
1295
  }
1352
1296
  else {
1353
1297
  // Genuinely no write endpoint (a deployed static site) — hand the file back.
@@ -1361,7 +1305,8 @@ export function createDocBrowser(options) {
1361
1305
  if (content === null) {
1362
1306
  window.alert(sourceUnauthorized
1363
1307
  ? 'This workspace needs an invite link before you can edit.\n\n' +
1364
- 'Run `tosijs-tunnel --link` on the machine hosting it and open the link once.'
1308
+ 'Run `tosijs-tunnel --link` on the machine hosting it and open the link once.\n\n' +
1309
+ 'If you opened this over the LAN (a `.local` or local IP address), invite links will not help: set `trustLan: true` in the site config, or use its tunnel URL instead.'
1365
1310
  : `Could not load source for ${doc.path}`);
1366
1311
  return;
1367
1312
  }
@@ -159,6 +159,17 @@ export function docSystemStyleSpec(theme = {}) {
159
159
  'tosi-doc-system[data-layout="full-screen"] > .doc-nav.doc-nav, tosi-doc-system[data-layout="full-screen"] > .doc-navbar.doc-navbar': {
160
160
  display: 'none',
161
161
  },
162
+ /*
163
+ `header: false` — no header bar on this page (#226). One variable takes the bar's space
164
+ away everywhere it is reserved (the pre-hydration inset, the nav's top, the hydrated
165
+ column), and the bar itself is hidden before hydration (`::before`) and after (`header`).
166
+ */
167
+ 'tosi-doc-system[data-no-header]': {
168
+ _headerHeight: '0px',
169
+ },
170
+ 'tosi-doc-system[data-no-header]::before, tosi-doc-system[data-no-header] header': {
171
+ display: 'none',
172
+ },
162
173
  '.darkmode': {
163
174
  ...invertLuminance(colors),
164
175
  /*
@@ -55,7 +55,7 @@ A self-contained, controllable embed (e.g. docs in a floating panel):
55
55
  </tosi-doc-system>
56
56
  ```
57
57
  */
58
- /*{ "parent": "Appendices" }*/
58
+ /*{ "parent": "Documentation System", "order": 40 }*/
59
59
  import { StyleSheet, elements, tosi, vars, withAttributes, } from 'tosijs';
60
60
  import * as tosijsModule from 'tosijs';
61
61
  import { createDocBrowser, } from '../doc-browser.js';
@@ -1,17 +1,11 @@
1
+ /*{ "parent": "Documentation System", "order": 80 }*/
1
2
  /*#
2
3
  # highlight-styles
3
4
 
4
- The syntax-highlighting token palette, on its own.
5
+ The syntax-highlighting token palette, on its own. The package ships no CSS files (every
6
+ style is a `StyleSheet()` call), so this is how token colours reach a page.
5
7
 
6
- It lives here rather than inside `docSystemStyleSpec` because the package ships **no CSS
7
- files at all** — every style is a `StyleSheet()` call — so before this existed, the only way
8
- to get token colours was to import the whole doc-site chrome. A standalone
9
- [`<tosi-highlight>`](/highlight-block/) on an ordinary page therefore produced perfectly
10
- correct `<span class="token …">` markup rendering in flat black, which is the one thing that
11
- component is for. The doc block even told you to "import the doc-system CSS", naming an
12
- import that did not exist.
13
-
14
- `<tosi-highlight>` now injects this itself on first use, so the standalone case needs nothing.
8
+ [`<tosi-highlight>`](/highlight-block/) injects it on first use, so that needs nothing.
15
9
  Import it directly only if you are highlighting markup you produced some other way:
16
10
 
17
11
  ```typescript
@@ -27,14 +21,15 @@ by setting its variable:
27
21
  :root { --token-keyword: rebeccapurple; }
28
22
  ```
29
23
 
30
- Two palettes, not one recomputed from the other: a light default and a dark set under
31
- `.darkmode`. Deriving light from dark literals is how this shipped failing WCAG AA on every
32
- token type. Measured, both directions (AA needs 4.5:1): light on `#fdfdfd` worst **5.32:1**
33
- (comment); dark on `#020202` worst **6.01:1** (punctuation). `doc-system-styles.test.ts`
34
- asserts both, so they are checked rather than claimed.
35
-
24
+ Two palettes: a light default and a dark set under `.darkmode`, each meeting WCAG AA
25
+ (4.5:1) against its own background. Light on `#fdfdfd` is at worst **5.32:1** (comment); dark
26
+ on `#020202` at worst **6.01:1** (punctuation). Tests check both.
27
+ */
28
+ /*
36
29
  Dark uses DESCENDANT selectors rather than a nested `.darkmode` block, because the doc-system
37
30
  spec already has one and a duplicate key loses silently to the last writer instead of merging.
31
+ Deriving the light palette from the dark literals is how this once shipped failing WCAG AA on
32
+ every token type; doc-system-styles.test.ts asserts both palettes.
38
33
  */
39
34
  import { StyleSheet, varDefault } from 'tosijs';
40
35
  /** The `.token.*` rules — light default plus a dark set under `.darkmode`. */
@@ -1,6 +1,15 @@
1
1
  export interface DocLike {
2
2
  filename: string;
3
+ /** this doc is the site root (`/`) instead of README; see `homeDoc` */
4
+ home?: boolean;
3
5
  }
6
+ /**
7
+ * The doc served at the site root: the one doc marked `home`, or README when none is.
8
+ * Marked by the build from the site's `home` option, or by a doc's own `{"home": true}`.
9
+ * The build refuses a corpus with more than one, so a second mark is ignored here rather
10
+ * than giving two docs the root.
11
+ */
12
+ export declare function homeDoc(docs: DocLike[]): string | null;
4
13
  /**
5
14
  * Build a deterministic { filename -> slug } map for a set of docs.
6
15
  *
@@ -11,10 +11,25 @@ function here is a pure, order-independent function of the docs array, so the tw
11
11
  sides always produce identical slugs.
12
12
  */
13
13
  const README = /^readme\.md$/i;
14
- /** filename without its final extension; README.md maps to '' (the site root) */
15
- function baseSlug(filename) {
16
- if (README.test(filename))
14
+ /**
15
+ * The doc served at the site root: the one doc marked `home`, or README when none is.
16
+ * Marked by the build from the site's `home` option, or by a doc's own `{"home": true}`.
17
+ * The build refuses a corpus with more than one, so a second mark is ignored here rather
18
+ * than giving two docs the root.
19
+ */
20
+ export function homeDoc(docs) {
21
+ const marked = docs.filter((doc) => doc.home);
22
+ return marked.length === 1 ? marked[0].filename : null;
23
+ }
24
+ /**
25
+ * filename without its final extension. The home doc maps to '' (the site root); README
26
+ * does too unless another doc is home, and is then `readme`.
27
+ */
28
+ function baseSlug(filename, home) {
29
+ if (filename === home)
17
30
  return '';
31
+ if (README.test(filename))
32
+ return home == null ? '' : 'readme';
18
33
  return filename.replace(/\.[^.]+$/, '');
19
34
  }
20
35
  /**
@@ -26,15 +41,16 @@ function baseSlug(filename) {
26
41
  * on array order.
27
42
  */
28
43
  export function buildSlugMap(docs) {
44
+ const home = homeDoc(docs);
29
45
  const counts = {};
30
46
  for (const doc of docs) {
31
- const base = baseSlug(doc.filename);
47
+ const base = baseSlug(doc.filename, home);
32
48
  counts[base] = (counts[base] || 0) + 1;
33
49
  }
34
50
  const map = {};
35
51
  for (const doc of docs) {
36
- const base = baseSlug(doc.filename);
37
- // README ('' base) is unique by construction; never disambiguate it.
52
+ const base = baseSlug(doc.filename, home);
53
+ // The root ('' base) is unique by construction; never disambiguate it.
38
54
  map[doc.filename] =
39
55
  base !== '' && counts[base] > 1 ? doc.filename.replace(/\./g, '-') : base;
40
56
  }
@@ -22,3 +22,13 @@ export declare function liveFenceLanguages(docs: Array<{
22
22
  text?: string;
23
23
  }>, policy?: ExamplePolicy): Set<string>;
24
24
  export declare function deadInPageAnchors(html: string): string[];
25
+ /**
26
+ * Links in a RENDERED page written as a bare page name (`[valueRenderer](value-renderer)`).
27
+ *
28
+ * Every page is served at `/slug/`, so a relative `value-renderer` resolves to
29
+ * `/this-page/value-renderer`, which is never a page: the link is broken with or without
30
+ * script. It is the natural thing to write and reads correctly in the source, which is how
31
+ * three of them shipped. Only a name with no `.` and no `/` is reported, so a relative asset
32
+ * (`diagram.svg`, `img/x.png`) is left alone. The fix is the root-relative `/value-renderer/`.
33
+ */
34
+ export declare function bareNameLinks(html: string): string[];
@@ -104,3 +104,27 @@ export function deadInPageAnchors(html) {
104
104
  }
105
105
  return [...dead];
106
106
  }
107
+ /**
108
+ * Links in a RENDERED page written as a bare page name (`[valueRenderer](value-renderer)`).
109
+ *
110
+ * Every page is served at `/slug/`, so a relative `value-renderer` resolves to
111
+ * `/this-page/value-renderer`, which is never a page: the link is broken with or without
112
+ * script. It is the natural thing to write and reads correctly in the source, which is how
113
+ * three of them shipped. Only a name with no `.` and no `/` is reported, so a relative asset
114
+ * (`diagram.svg`, `img/x.png`) is left alone. The fix is the root-relative `/value-renderer/`.
115
+ */
116
+ export function bareNameLinks(html) {
117
+ const found = new Set();
118
+ for (const tag of html.matchAll(START_TAG)) {
119
+ if (tag[1].toLowerCase() !== 'a')
120
+ continue;
121
+ for (const attr of tag[2].matchAll(ATTRIBUTE)) {
122
+ if (attr[1].toLowerCase() !== 'href')
123
+ continue;
124
+ const value = unescapeAttr(attr[2] ?? attr[3] ?? attr[4] ?? '');
125
+ if (/^[A-Za-z0-9_-]+\/?$/.test(value))
126
+ found.add(value);
127
+ }
128
+ }
129
+ return [...found];
130
+ }
@@ -287,6 +287,13 @@ export declare function mayWriteSource(opts: {
287
287
  * never be the permissive-by-accident case for anything but the status quo.
288
288
  */
289
289
  sessionMayWrite?: boolean;
290
+ /**
291
+ * The project opted in to trusting its LAN (`trustLan`, #227): a direct, unproxied peer at a
292
+ * private address counts as this machine. Off unless the caller says so.
293
+ */
294
+ trustLan?: boolean;
295
+ /** did the request carry forwarding headers? A proxied request is never LAN trust */
296
+ proxied?: boolean;
290
297
  }): boolean;
291
298
  /**
292
299
  * May the holder of this session token write?
@@ -329,6 +336,12 @@ export declare function mayDriveWithAgent(opts: {
329
336
  * dual-stack listener, and missing it would lock out the local machine on some setups.
330
337
  */
331
338
  export declare function isLoopbackAddressForAuth(address: string | undefined | null): boolean;
339
+ /**
340
+ * A private-network peer: RFC 1918 IPv4 (10/8, 172.16/12, 192.168/16), IPv6 unique-local
341
+ * (fc00::/7) or link-local (fe80::/10), including IPv4 mapped into IPv6. Loopback is not
342
+ * included; ask `isLoopbackAddressForAuth` for that. Used ONLY behind the `trustLan` opt-in.
343
+ */
344
+ export declare function isPrivateNetworkAddress(address: string | undefined | null): boolean;
332
345
  export declare function isProxiedRequest(headers: {
333
346
  get(name: string): string | null;
334
347
  }): boolean;