tosijs-ui 1.12.2 → 1.12.4
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.
- package/CHANGELOG.md +119 -0
- package/dist/doc-browser.d.ts +2 -2
- package/dist/doc-browser.js +88 -3
- package/dist/doc-system/doc-system-styles.js +46 -1
- package/dist/doc-system/site/dev-server.js +28 -2
- package/dist/doc-system/site/docs.d.ts +6 -7
- package/dist/doc-system/site/docs.js +79 -5
- package/dist/doc-system/site/make-llms-txt.js +8 -1
- package/dist/iife.js +79 -79
- package/dist/iife.js.map +6 -6
- package/dist/side-nav.d.ts +11 -0
- package/dist/side-nav.js +65 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.12.4
|
|
4
|
+
|
|
5
|
+
### `.tjs` source files are documented again ([#108](https://github.com/tonioloewald/tosijs-ui/issues/108))
|
|
6
|
+
|
|
7
|
+
Converting a source file from `.ts` to native `.tjs` **silently deleted its documentation page**,
|
|
8
|
+
and its `llms.txt` entry with it. Reported from tosijs, mid-port: one page lost per converted
|
|
9
|
+
module.
|
|
10
|
+
|
|
11
|
+
The silence was the real defect. The build exited 0, and the internal-link check passed —
|
|
12
|
+
a page that was never generated is linked from nowhere, so "41 slugs, no 404s" is a _pass_. The
|
|
13
|
+
only signal was a slug count nothing asserts on. `'x.tjs'.endsWith('.ts')` is `false`, so the
|
|
14
|
+
`llms.txt` filter excluded it too, without even the mercy of an accidental match.
|
|
15
|
+
|
|
16
|
+
`.tjs` is now scraped, in both places. The doc-block syntax is identical across these languages —
|
|
17
|
+
a doc block is just a comment — so there is nothing language-specific to support. The list _is_
|
|
18
|
+
the feature, which is exactly why an omission from it is invisible.
|
|
19
|
+
|
|
20
|
+
**And a guard for the next one.** A file carrying what looks like a doc block, in an extension we
|
|
21
|
+
do not scrape, is now **named** in the build output rather than passed over:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
⚠️ 1 file(s) look documented but are not scraped, so they have no page:
|
|
25
|
+
src/thing.vue
|
|
26
|
+
Scraped extensions: .ts, .js, .tjs, .css. Add one to SCRAPED_SOURCE_EXTENSIONS if it should be documented.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
It warns rather than fails: a file may legitimately contain the sequence without wanting to be
|
|
30
|
+
documentation, and a doc site that refused to build over a comment would be worse than the bug.
|
|
31
|
+
But whatever the next extension turns out to be, its absence can no longer happen quietly.
|
|
32
|
+
|
|
33
|
+
## 1.12.3
|
|
34
|
+
|
|
35
|
+
### Back now has layout coverage, because a reader noticed before the tests did
|
|
36
|
+
|
|
37
|
+
Reported from the keyboard as "sometimes the full-screen page and the full-screen page with the
|
|
38
|
+
nav open look like different history entries". Measured, it is neither history nor
|
|
39
|
+
inconsistent: the nav toggle creates **no** history entry (`history.length` unchanged across
|
|
40
|
+
repeated toggles, URL unchanged). What happens is that **Back re-asserts the page's declared
|
|
41
|
+
layout** and discards the override — so leaving a page with the nav open and returning finds it
|
|
42
|
+
closed.
|
|
43
|
+
|
|
44
|
+
That is defensible and it is what ships: a full-screen page is full-screen whenever you arrive
|
|
45
|
+
at it. Whether Back should instead restore what you left is now an open decision in `TODO.md`,
|
|
46
|
+
with the measurements attached, rather than whatever fell out of the implementation.
|
|
47
|
+
|
|
48
|
+
The gap the report exposed was in the tests, not the feature: `doc-system.pw.ts` already covered
|
|
49
|
+
Back for URL, title and content, but nothing checked its effect on **layout**, and every layout
|
|
50
|
+
test reached its pages by clicking links. Both directions are covered now — and labelled by what
|
|
51
|
+
they actually do, since one is a genuine regression test (dropping `removeAttribute('data-layout')`
|
|
52
|
+
on navigation fails it) and the other is a contract tripwire that survives every mutation tried
|
|
53
|
+
against it. A test that cannot fail is usually decoration; that one is deliberate, and saying so
|
|
54
|
+
is the difference.
|
|
55
|
+
|
|
56
|
+
### The dev server sends `Cache-Control: no-store` on everything
|
|
57
|
+
|
|
58
|
+
It previously sent no `Cache-Control` at all, and that is not neutral: a browser may invent a
|
|
59
|
+
freshness lifetime when you decline to state one, and Safari is the most willing to. So the dev
|
|
60
|
+
server would rebuild correctly while the browser kept serving the previous build — which
|
|
61
|
+
presents as "my fix did not work", and is only escaped by emptying the cache by hand. It cost
|
|
62
|
+
exactly that here: a bug was reported against code that had already been fixed.
|
|
63
|
+
|
|
64
|
+
All three of the file-server's exits are covered — injected HTML, compressed assets, and
|
|
65
|
+
binaries that stream untouched — because they are separate responses and adding the header to
|
|
66
|
+
the obvious one is precisely how the other two get missed. Each is independently
|
|
67
|
+
mutation-verified.
|
|
68
|
+
|
|
69
|
+
**Dev server only.** The built site in `docs/` is unaffected; it is a static site for a real
|
|
70
|
+
host, and telling a CDN never to store it would be actively wrong.
|
|
71
|
+
|
|
72
|
+
### `layout: "full-screen"` — and `<tosi-sidenav alwaysCompact>` underneath it
|
|
73
|
+
|
|
74
|
+
`full-width` shipped in 1.12.2 and `full-screen` did not, because it needed something from the
|
|
75
|
+
sidenav that did not exist. It exists now, and it is one line rather than a new layout.
|
|
76
|
+
|
|
77
|
+
**`<tosi-sidenav>` gained `navVisible`** — the one control a "show me the navigation" button
|
|
78
|
+
needs. Read it, flip it:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
sidenav.navVisible = !sidenav.navVisible
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
It resolves what hiding the nav _means_ at the current width — forcing compact on a wide screen,
|
|
85
|
+
simply showing the content on a narrow one — so no caller has to know, and a narrow-screen
|
|
86
|
+
request does not outstay itself when the window is widened. `layout: "full-screen"` uses the
|
|
87
|
+
same property, so the feature is general rather than something the doc-browser knows a trick
|
|
88
|
+
about.
|
|
89
|
+
|
|
90
|
+
**And `alwaysCompact` underneath it.** Compact mode already shows the nav _or_ the content
|
|
91
|
+
and takes turns between them — which is precisely what a full-screen page wants — so this just
|
|
92
|
+
removes the width test rather than adding a second way to lay out. It is a named state and not a
|
|
93
|
+
`minSize` no viewport can reach, because the second one works and reads as a bug to the next
|
|
94
|
+
person.
|
|
95
|
+
|
|
96
|
+
**`layout: "full-screen"`** then means: no reading measure, no nav, no gutter, and a content area
|
|
97
|
+
with a real height — so a demo, a canvas or an embedded app can ask for `height: 100%` and get
|
|
98
|
+
it. There is a [demo page](/full-screen-demo/), and the navigation button works there: it
|
|
99
|
+
returns you to the normal layout rather than replacing the content with a full-screen nav, and
|
|
100
|
+
it **stays offered** so you can go back. Riding `compact` directly, as it used to, meant the
|
|
101
|
+
button vanished on its own click and stranded you in the normal layout.
|
|
102
|
+
|
|
103
|
+
Two inline styles were quietly breaking the promise. `.doc-content` receives both its
|
|
104
|
+
`max-width` and its `padding` inline from the doc-browser, so no stylesheet could override
|
|
105
|
+
either — a full-screen page rendered inset by 32px, in a box that collapsed to its content
|
|
106
|
+
height instead of filling. `padding` now routes through `--doc-content-padding`, the way the
|
|
107
|
+
measure already did.
|
|
108
|
+
|
|
109
|
+
Navigating away restores the nav, and a reader's override lasts until they navigate rather than
|
|
110
|
+
beyond it — **including Back**, so a full-screen page is full-screen whenever you arrive at it.
|
|
111
|
+
That is predictable, but it does mean leaving a page with the nav open and returning finds it
|
|
112
|
+
closed; whether Back should instead restore what you left is noted in `TODO.md` as a decision
|
|
113
|
+
rather than left to whatever fell out of the implementation. Both directions are mutation-verified, which caught a test that _looked_ like it
|
|
114
|
+
covered the second one and did not: it routed through a prose page, which reset the state as a
|
|
115
|
+
side effect.
|
|
116
|
+
|
|
117
|
+
Before hydration there is no sidenav at all, just static markup, so a stylesheet rule covers the
|
|
118
|
+
first paint. Those rules select `.doc-nav.doc-nav` — the class deliberately repeated — because
|
|
119
|
+
the pre-hydration layout further down the same sheet has identical specificity, and winning on
|
|
120
|
+
source order works right up until someone reorders the spec.
|
|
121
|
+
|
|
3
122
|
## 1.12.2
|
|
4
123
|
|
|
5
124
|
### `haltijaDev: 'tunnel'` — let an agent drive a page running in a headset (#104)
|
package/dist/doc-browser.d.ts
CHANGED
|
@@ -22,8 +22,8 @@ export interface Doc {
|
|
|
22
22
|
path: string;
|
|
23
23
|
pin?: string;
|
|
24
24
|
hidden?: boolean;
|
|
25
|
-
/** `'full-width'` drops the reading measure
|
|
26
|
-
layout?: 'full-width';
|
|
25
|
+
/** `'full-width'` drops the reading measure; `'full-screen'` also puts the nav away. */
|
|
26
|
+
layout?: 'full-width' | 'full-screen';
|
|
27
27
|
testStatus?: 'passed' | 'failed' | 'pending';
|
|
28
28
|
bakes?: Array<[string, {
|
|
29
29
|
dialect: string;
|
package/dist/doc-browser.js
CHANGED
|
@@ -422,6 +422,16 @@ export function createDocBrowser(options) {
|
|
|
422
422
|
docs,
|
|
423
423
|
currentDoc,
|
|
424
424
|
compact: false,
|
|
425
|
+
/*
|
|
426
|
+
Whether to OFFER the navigation button — deliberately not the same thing as `compact`.
|
|
427
|
+
|
|
428
|
+
The button used to ride `compact` directly, which is right for a responsive page: wide
|
|
429
|
+
enough to show the nav beside the content, and there is nothing to toggle. But opening the
|
|
430
|
+
nav on a full-screen page leaves compact mode by design, and the button would vanish with
|
|
431
|
+
it — stranding the reader in the normal layout with no way back to full screen. So it is
|
|
432
|
+
offered whenever the layout is compact OR the page asked to be full-screen.
|
|
433
|
+
*/
|
|
434
|
+
navToggle: false,
|
|
425
435
|
},
|
|
426
436
|
});
|
|
427
437
|
const app = state[stateKey];
|
|
@@ -576,13 +586,58 @@ export function createDocBrowser(options) {
|
|
|
576
586
|
// sidenav')`, which returns the FIRST sidenav in the document (an OUTER instance
|
|
577
587
|
// when this one is an embedded, memory-routed browser).
|
|
578
588
|
let sidenav = null;
|
|
589
|
+
/*
|
|
590
|
+
`full-screen` uses the sidenav's OWN compact behaviour rather than a new state.
|
|
591
|
+
|
|
592
|
+
`<tosi-sidenav>` already does exactly what full-screen wants: compact mode shows the nav OR the
|
|
593
|
+
content, with `contentVisible` picking which. `alwaysCompact` keeps it in that mode at any
|
|
594
|
+
width, and `contentVisible` gives the page the whole box.
|
|
595
|
+
|
|
596
|
+
Remembered rather than applied directly, because the sidenav is not there yet when the first
|
|
597
|
+
doc renders — `sidenav` is captured from its own change event, which fires on a state CHANGE
|
|
598
|
+
and so may never fire at all on a normal-width page.
|
|
599
|
+
*/
|
|
600
|
+
let wantsFullScreen = false;
|
|
601
|
+
/*
|
|
602
|
+
What we last PUSHED onto the sidenav, so the user's own toggle is not overwritten.
|
|
603
|
+
|
|
604
|
+
The first version applied the layout on every call, and `applyFullScreen` is called from the
|
|
605
|
+
sidenav's change handler — so clicking "navigation" flipped `contentVisible`, the sidenav
|
|
606
|
+
changed state, the handler fired, and the layout was re-applied on top. The nav button looked
|
|
607
|
+
live and did nothing. Applying only on TRANSITION means a full-screen page sets itself up once
|
|
608
|
+
and then leaves the reader alone.
|
|
609
|
+
*/
|
|
610
|
+
let appliedFullScreen = null;
|
|
611
|
+
/** Offer the button when there is something to toggle: a compact layout, or a full-screen page. */
|
|
612
|
+
const refreshNavToggle = () => {
|
|
613
|
+
app.navToggle = (Boolean(sidenav?.compact) || wantsFullScreen);
|
|
614
|
+
};
|
|
615
|
+
const applyFullScreen = () => {
|
|
616
|
+
/*
|
|
617
|
+
Resolved from THIS browser's own container when the change event has not introduced it yet.
|
|
618
|
+
Scoped rather than `document.querySelector('tosi-sidenav')`, which returns the first in the
|
|
619
|
+
document — for a nested doc-browser, the HOST's.
|
|
620
|
+
*/
|
|
621
|
+
if (!sidenav && typeof container !== 'undefined' && container) {
|
|
622
|
+
sidenav = container.querySelector(TosiSidenav.tagName);
|
|
623
|
+
}
|
|
624
|
+
if (!sidenav)
|
|
625
|
+
return;
|
|
626
|
+
if (appliedFullScreen === wantsFullScreen)
|
|
627
|
+
return;
|
|
628
|
+
// The same one control the nav button uses — the layout is just asking for the nav to be
|
|
629
|
+
// away, and the sidenav decides how that is achieved.
|
|
630
|
+
sidenav.navVisible = !wantsFullScreen;
|
|
631
|
+
appliedFullScreen = wantsFullScreen;
|
|
632
|
+
refreshNavToggle();
|
|
633
|
+
};
|
|
579
634
|
const headerContent = [
|
|
580
635
|
button({
|
|
581
636
|
class: 'iconic',
|
|
582
637
|
style: { color: vars.linkColor },
|
|
583
638
|
title: 'navigation',
|
|
584
639
|
bind: {
|
|
585
|
-
value: app.
|
|
640
|
+
value: app.navToggle,
|
|
586
641
|
binding: {
|
|
587
642
|
toDOM(element, compact) {
|
|
588
643
|
element.style.display = compact ? '' : 'none';
|
|
@@ -593,8 +648,10 @@ export function createDocBrowser(options) {
|
|
|
593
648
|
},
|
|
594
649
|
},
|
|
595
650
|
onClick() {
|
|
651
|
+
// Read it, flip it. `<tosi-sidenav>` resolves what that means at this width; a
|
|
652
|
+
// button should not have to know, and this one used to.
|
|
596
653
|
if (sidenav)
|
|
597
|
-
sidenav.
|
|
654
|
+
sidenav.navVisible = !sidenav.navVisible;
|
|
598
655
|
},
|
|
599
656
|
}, icons.menu()),
|
|
600
657
|
span({ style: { flex: '0 0 10px' } }),
|
|
@@ -651,7 +708,13 @@ export function createDocBrowser(options) {
|
|
|
651
708
|
// doc-browser (no doc-system stylesheet) working.
|
|
652
709
|
maxWidth: 'var(--doc-content-max-width, 44em)',
|
|
653
710
|
margin: 'auto',
|
|
654
|
-
|
|
711
|
+
/*
|
|
712
|
+
Through a variable, for the same reason `maxWidth` is: this is an INLINE style, so a
|
|
713
|
+
stylesheet cannot override it, and `layout: "full-screen"` needs to. A full-screen page that
|
|
714
|
+
keeps a 1em gutter is not full screen — it is a demo inset by 32px, which is exactly what it
|
|
715
|
+
looked like.
|
|
716
|
+
*/
|
|
717
|
+
padding: 'var(--doc-content-padding, 0 1em)',
|
|
655
718
|
overflow: 'hidden',
|
|
656
719
|
});
|
|
657
720
|
// Adoption is zero-flash hydration of a statically pre-rendered page: it only
|
|
@@ -743,6 +806,25 @@ export function createDocBrowser(options) {
|
|
|
743
806
|
else
|
|
744
807
|
layoutHost.removeAttribute('data-layout');
|
|
745
808
|
}
|
|
809
|
+
/*
|
|
810
|
+
`full-screen` also collapses the nav, and that has to go through the COMPONENT rather than
|
|
811
|
+
CSS: `<tosi-sidenav>` writes its column widths as inline properties, so a stylesheet has
|
|
812
|
+
nothing to override. The pre-hydration rule in doc-system-styles.ts handles the static
|
|
813
|
+
markup, which has a plain `<nav>` and no sidenav yet; this handles the hydrated page. Two
|
|
814
|
+
rules for two genuinely different DOMs, not two fixes for one bug.
|
|
815
|
+
*/
|
|
816
|
+
wantsFullScreen = doc.layout === 'full-screen';
|
|
817
|
+
/*
|
|
818
|
+
Forced on every navigation. Without this, opening the nav on one full-screen page (which
|
|
819
|
+
turns `alwaysCompact` off) would leave the NEXT full-screen page not full-screen, because
|
|
820
|
+
the wish had not changed and the transition check would skip it. A reader's override is
|
|
821
|
+
meant to last until they navigate, not beyond it.
|
|
822
|
+
*/
|
|
823
|
+
appliedFullScreen = null;
|
|
824
|
+
applyFullScreen();
|
|
825
|
+
// applyFullScreen returns early when there is no sidenav yet; the button state still has
|
|
826
|
+
// to follow the doc, so this does not ride on that path succeeding.
|
|
827
|
+
refreshNavToggle();
|
|
746
828
|
}
|
|
747
829
|
rewriteContentLinks();
|
|
748
830
|
// Stamp each example with its source file (for the source↔doc map). doc.path
|
|
@@ -1249,6 +1331,9 @@ export function createDocBrowser(options) {
|
|
|
1249
1331
|
sidenav = event.target.closest(TosiSidenav.tagName);
|
|
1250
1332
|
if (sidenav)
|
|
1251
1333
|
app.compact = sidenav.compact;
|
|
1334
|
+
refreshNavToggle();
|
|
1335
|
+
// The sidenav has just introduced itself; give it the layout the doc asked for.
|
|
1336
|
+
applyFullScreen();
|
|
1252
1337
|
},
|
|
1253
1338
|
}, searchField, navContent, div({
|
|
1254
1339
|
style: {
|
|
@@ -108,6 +108,51 @@ export function docSystemStyleSpec(theme = {}) {
|
|
|
108
108
|
'tosi-doc-system[data-layout="full-width"]': {
|
|
109
109
|
_docContentMaxWidth: 'none',
|
|
110
110
|
},
|
|
111
|
+
/*
|
|
112
|
+
`full-screen` — no measure, and no chrome.
|
|
113
|
+
|
|
114
|
+
These rules are for the PRE-HYDRATION page, whose markup is a plain `<nav class="doc-nav">`
|
|
115
|
+
with no inline styles, so hiding it here works and works on first paint. Once the bundle
|
|
116
|
+
loads, the nav becomes a slotted node inside `<tosi-sidenav>` carrying its own inline
|
|
117
|
+
`display`, and CSS can no longer touch it — the doc-browser sets `sidenav.navHidden`
|
|
118
|
+
instead. Different DOM before and after hydration, so genuinely two rules rather than a
|
|
119
|
+
belt-and-braces duplicate.
|
|
120
|
+
*/
|
|
121
|
+
'tosi-doc-system[data-layout="full-screen"]': {
|
|
122
|
+
_docContentMaxWidth: 'none',
|
|
123
|
+
},
|
|
124
|
+
'tosi-doc-system[data-layout="full-screen"] .doc-content': {
|
|
125
|
+
padding: '0',
|
|
126
|
+
/*
|
|
127
|
+
A definite height, so a child asking for `height: 100%` gets one.
|
|
128
|
+
|
|
129
|
+
"The content is the viewport" is the promise this layout makes, and a demo, a canvas or an
|
|
130
|
+
embedded app is the reason to use it — all of which want to FILL, not to sit in a tall
|
|
131
|
+
box. Without a height here `100%` on a child resolves against `auto` and collapses to its
|
|
132
|
+
content.
|
|
133
|
+
*/
|
|
134
|
+
height: '100%',
|
|
135
|
+
overflow: 'auto',
|
|
136
|
+
/*
|
|
137
|
+
The gutter goes too. `.doc-content`'s padding is an INLINE style set by the doc-browser,
|
|
138
|
+
so this is set through the variable it reads rather than declared here, where it would
|
|
139
|
+
simply lose.
|
|
140
|
+
*/
|
|
141
|
+
_docContentPadding: '0',
|
|
142
|
+
},
|
|
143
|
+
/*
|
|
144
|
+
`.doc-nav.doc-nav` — the class twice, on purpose.
|
|
145
|
+
|
|
146
|
+
`tosi-doc-system:not(:defined) .doc-nav` (the pre-hydration layout, further down this same
|
|
147
|
+
sheet) has IDENTICAL specificity to the single-class form, so which one wins is decided by
|
|
148
|
+
source order, and it sits later. Winning on position works until someone reorders the spec,
|
|
149
|
+
at which point the nav quietly comes back on full-screen pages with nothing to point at.
|
|
150
|
+
Repeating the class costs one selector token and settles it on specificity, wherever these
|
|
151
|
+
rules end up living.
|
|
152
|
+
*/
|
|
153
|
+
'tosi-doc-system[data-layout="full-screen"] > .doc-nav.doc-nav, tosi-doc-system[data-layout="full-screen"] > .doc-navbar.doc-navbar': {
|
|
154
|
+
display: 'none',
|
|
155
|
+
},
|
|
111
156
|
'.darkmode': {
|
|
112
157
|
...invertLuminance(colors),
|
|
113
158
|
/*
|
|
@@ -190,7 +235,7 @@ export function docSystemStyleSpec(theme = {}) {
|
|
|
190
235
|
display: 'block',
|
|
191
236
|
maxWidth: vars.docContentMaxWidth,
|
|
192
237
|
margin: 'auto',
|
|
193
|
-
padding: '0 1em',
|
|
238
|
+
padding: 'var(--doc-content-padding, 0 1em)',
|
|
194
239
|
// Must match doc-browser's inline style EXACTLY, `overflow` included: it
|
|
195
240
|
// establishes a block formatting context, which stops the <h1>'s top margin
|
|
196
241
|
// collapsing out of the box. Without it the static content sits 10px lower
|
|
@@ -832,6 +832,29 @@ export async function devServer(config, opts = {}) {
|
|
|
832
832
|
});
|
|
833
833
|
}
|
|
834
834
|
// disk, and non-HTML assets are streamed untouched.
|
|
835
|
+
/*
|
|
836
|
+
NOTHING the dev server serves may be cached. Ever.
|
|
837
|
+
|
|
838
|
+
None of the file paths below sent `Cache-Control`, so browsers applied HEURISTIC caching —
|
|
839
|
+
they are free to invent a freshness lifetime when you decline to state one, and Safari is the
|
|
840
|
+
most willing to. The result is a dev server that rebuilds correctly while the browser keeps
|
|
841
|
+
showing the previous build, which presents as "the fix did not work" and costs whoever is
|
|
842
|
+
looking at it far more than a re-download. It cost exactly that here: a bug was reported
|
|
843
|
+
against code that had already been fixed.
|
|
844
|
+
|
|
845
|
+
`no-store` rather than `no-cache`: no-cache still stores and revalidates, which needs
|
|
846
|
+
validators we do not emit, and a dev server has nothing to gain from the round trip. The cost
|
|
847
|
+
is re-fetching the bundle on a full page load — a doc site navigates client-side after that,
|
|
848
|
+
and the responses are brotli/gzip compressed anyway.
|
|
849
|
+
|
|
850
|
+
Deliberately NOT applied to the built output: `docs/` is a static site for a real host, and
|
|
851
|
+
telling a CDN never to store it would be actively wrong. This is the dev server only.
|
|
852
|
+
*/
|
|
853
|
+
const NO_CACHE = {
|
|
854
|
+
'Cache-Control': 'no-store, must-revalidate',
|
|
855
|
+
Pragma: 'no-cache',
|
|
856
|
+
Expires: '0',
|
|
857
|
+
};
|
|
835
858
|
async function respondFile(filePath, request, viaTunnel = false) {
|
|
836
859
|
/*
|
|
837
860
|
WHICH loader, decided per request rather than once at boot.
|
|
@@ -864,6 +887,7 @@ export async function devServer(config, opts = {}) {
|
|
|
864
887
|
return new Response(body, {
|
|
865
888
|
headers: {
|
|
866
889
|
'Content-Type': 'text/html; charset=utf-8',
|
|
890
|
+
...NO_CACHE,
|
|
867
891
|
...(encoding
|
|
868
892
|
? { 'Content-Encoding': encoding, Vary: 'Accept-Encoding' }
|
|
869
893
|
: {}),
|
|
@@ -881,14 +905,16 @@ export async function devServer(config, opts = {}) {
|
|
|
881
905
|
return new Response(bytes, {
|
|
882
906
|
headers: {
|
|
883
907
|
'Content-Type': file.type || 'application/octet-stream',
|
|
908
|
+
...NO_CACHE,
|
|
884
909
|
'Content-Encoding': encoding,
|
|
885
910
|
Vary: 'Accept-Encoding',
|
|
886
911
|
},
|
|
887
912
|
});
|
|
888
913
|
}
|
|
889
914
|
// Everything else streams untouched — images, fonts, epubs and glb are already
|
|
890
|
-
// compressed, and re-encoding them only makes them bigger.
|
|
891
|
-
|
|
915
|
+
// compressed, and re-encoding them only makes them bigger. Still uncacheable: an icon or a
|
|
916
|
+
// font you just changed is no more welcome stale than a script is.
|
|
917
|
+
return new Response(Bun.file(filePath), { headers: { ...NO_CACHE } });
|
|
892
918
|
}
|
|
893
919
|
async function handleTestReport(request) {
|
|
894
920
|
try {
|
|
@@ -45,14 +45,12 @@ export interface Doc {
|
|
|
45
45
|
* frontmatter. Unset means the reading column, so nothing changes for a corpus that does not
|
|
46
46
|
* ask.
|
|
47
47
|
*
|
|
48
|
-
* `'full-screen'`
|
|
49
|
-
*
|
|
50
|
-
* width and the nav's own `display` are both
|
|
51
|
-
*
|
|
52
|
-
* exactly where it was is the kind of partial success that gets reported as a bug. Tracked in
|
|
53
|
-
* TODO.md.
|
|
48
|
+
* `'full-screen'` drops the measure AND puts the nav away, so the page is the whole viewport —
|
|
49
|
+
* for a demo, a canvas, an embedded app. It needed `<tosi-sidenav navHidden>` to exist first:
|
|
50
|
+
* the nav width and the nav's own `display` are both INLINE styles, so no stylesheet could
|
|
51
|
+
* reach them from out here.
|
|
54
52
|
*/
|
|
55
|
-
layout?: 'full-width';
|
|
53
|
+
layout?: 'full-width' | 'full-screen';
|
|
56
54
|
headTitle?: string;
|
|
57
55
|
description?: string;
|
|
58
56
|
keywords?: string | string[];
|
|
@@ -84,5 +82,6 @@ export declare function parseFrontmatter(content: string): {
|
|
|
84
82
|
data: Partial<Doc>;
|
|
85
83
|
body: string;
|
|
86
84
|
};
|
|
85
|
+
export declare const SCRAPED_SOURCE_EXTENSIONS: string[];
|
|
87
86
|
export declare function extractDocs(options: ExtractDocsOptions): Doc[];
|
|
88
87
|
export declare function saveDocsJSON(docs: Doc[], outputPath: string): void;
|
|
@@ -150,11 +150,8 @@ export function parseFrontmatter(content) {
|
|
|
150
150
|
`wide` — and silently keeping the reading column is the behaviour that gets reported as
|
|
151
151
|
"the metadata does nothing", so say so instead.
|
|
152
152
|
*/
|
|
153
|
-
if (val === 'full-width')
|
|
153
|
+
if (val === 'full-width' || val === 'full-screen')
|
|
154
154
|
data.layout = val;
|
|
155
|
-
else if (val === 'full-screen')
|
|
156
|
-
console.error(`layout: full-screen is not implemented yet (see TODO.md) — ignoring it rather than ` +
|
|
157
|
-
`dropping the measure and leaving the chrome in place`);
|
|
158
155
|
else if (val)
|
|
159
156
|
console.error(`unknown layout ${JSON.stringify(val)} in frontmatter`);
|
|
160
157
|
}
|
|
@@ -184,6 +181,53 @@ function metadata(content, filePath) {
|
|
|
184
181
|
delete data.title;
|
|
185
182
|
return data;
|
|
186
183
|
}
|
|
184
|
+
/*
|
|
185
|
+
Source extensions whose doc-comment blocks become documentation.
|
|
186
|
+
|
|
187
|
+
`.tjs` earns its place for a reason worth recording: converting `more-math.ts` to `more-math.tjs`
|
|
188
|
+
in tosijs SILENTLY deleted its documentation page (tosijs-ui#108). The build stayed green, and so
|
|
189
|
+
did the internal-link check — a page that was never generated is linked from nowhere, so "41
|
|
190
|
+
slugs, no 404s" is a pass. The only signal was a slug count nobody asserts on. A project mid-port
|
|
191
|
+
loses one page per converted module and hears about it from a reader.
|
|
192
|
+
|
|
193
|
+
The block syntax is identical across all of these, because a doc block is just a comment. There
|
|
194
|
+
is nothing language-specific to support; the list IS the whole feature, which is exactly why
|
|
195
|
+
forgetting an entry is silent.
|
|
196
|
+
|
|
197
|
+
(Written without the closing-delimiter characters spelled out, because putting them in a comment
|
|
198
|
+
ends it — the same trap tosijs-ui#70 records for doc blocks, hit again while writing this.)
|
|
199
|
+
|
|
200
|
+
See `warnUnscrapedDocBlocks` below: a missing extension should never again be invisible.
|
|
201
|
+
*/
|
|
202
|
+
export const SCRAPED_SOURCE_EXTENSIONS = ['.ts', '.js', '.tjs', '.css'];
|
|
203
|
+
/** Files that look documented but are not scraped — reported once at the end of a walk. */
|
|
204
|
+
const unscrapedDocBlocks = [];
|
|
205
|
+
/**
|
|
206
|
+
* Does an UNSCRAPED file appear to carry a doc block?
|
|
207
|
+
*
|
|
208
|
+
* Deliberately cheap and deliberately narrow: only files we are not already reading, only
|
|
209
|
+
* plausible source extensions, and only the same line-starting pattern the scraper itself uses,
|
|
210
|
+
* so this cannot disagree with it about what a doc block is. Binary files and anything large are
|
|
211
|
+
* skipped — this is a safety net, not a search.
|
|
212
|
+
*/
|
|
213
|
+
function hasDocBlock(filePath) {
|
|
214
|
+
const ext = path.extname(filePath);
|
|
215
|
+
if (SCRAPED_SOURCE_EXTENSIONS.includes(ext) || ext === '.md')
|
|
216
|
+
return false;
|
|
217
|
+
// Extensions worth checking: source-shaped, and not the ones we already read.
|
|
218
|
+
if (!/^\.[cm]?[jt]sx?$|^\.(tjs|ajs|svelte|vue|scss|sass|less)$/.test(ext)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const stat = fs.statSync(filePath);
|
|
223
|
+
if (stat.size > 2_000_000)
|
|
224
|
+
return false;
|
|
225
|
+
return /^[ \t]*\/\*#/m.test(fs.readFileSync(filePath, 'utf8'));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return false; // unreadable is not our business here
|
|
229
|
+
}
|
|
230
|
+
}
|
|
187
231
|
function findMarkdownFiles(paths, ignore) {
|
|
188
232
|
const markdownFiles = [];
|
|
189
233
|
const truncationFound = [];
|
|
@@ -226,7 +270,22 @@ function findMarkdownFiles(paths, ignore) {
|
|
|
226
270
|
...fm, // frontmatter wins over JSON-comment metadata + the H1
|
|
227
271
|
});
|
|
228
272
|
}
|
|
229
|
-
else if (
|
|
273
|
+
else if (hasDocBlock(filePath)) {
|
|
274
|
+
/*
|
|
275
|
+
A file we do NOT scrape, carrying what looks like a doc block.
|
|
276
|
+
|
|
277
|
+
This is the guard for the whole class, not just for `.tjs`. The extension list is the
|
|
278
|
+
entire feature, so an omission from it is invisible by construction: the page is simply
|
|
279
|
+
never generated, nothing links to it, and the link check passes. tosijs lost a page per
|
|
280
|
+
ported module this way and found out from a reader (tosijs-ui#108).
|
|
281
|
+
|
|
282
|
+
Warn rather than fail. A file may legitimately contain the sequence without wanting to be
|
|
283
|
+
documentation, and a doc site that refuses to build over a comment would be worse than
|
|
284
|
+
the bug. But it can no longer happen in silence.
|
|
285
|
+
*/
|
|
286
|
+
unscrapedDocBlocks.push(path.relative(process.cwd(), filePath));
|
|
287
|
+
}
|
|
288
|
+
else if (SCRAPED_SOURCE_EXTENSIONS.includes(path.extname(file))) {
|
|
230
289
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
231
290
|
// A /*# … */ block is only a doc when it STARTS a line (whitespace-only
|
|
232
291
|
// before the slash). This keeps a `/*#` that appears inside a // comment,
|
|
@@ -284,6 +343,21 @@ function findMarkdownFiles(paths, ignore) {
|
|
|
284
343
|
if (truncationFound.length) {
|
|
285
344
|
console.warn('\n' + formatTruncationWarnings(truncationFound));
|
|
286
345
|
}
|
|
346
|
+
/*
|
|
347
|
+
Same policy, different failure: a file that looks documented and is not being read.
|
|
348
|
+
|
|
349
|
+
Named individually rather than counted, because the useful version of this message is one you
|
|
350
|
+
can act on without going looking. The fix is nearly always one entry in
|
|
351
|
+
SCRAPED_SOURCE_EXTENSIONS.
|
|
352
|
+
*/
|
|
353
|
+
if (unscrapedDocBlocks.length) {
|
|
354
|
+
const list = unscrapedDocBlocks.map((f) => ` ${f}`).join('\n');
|
|
355
|
+
console.warn(`\n⚠️ ${unscrapedDocBlocks.length} file(s) look documented but are not scraped, so ` +
|
|
356
|
+
`they have no page:\n${list}\n` +
|
|
357
|
+
` Scraped extensions: ${SCRAPED_SOURCE_EXTENSIONS.join(', ')}. ` +
|
|
358
|
+
`Add one to SCRAPED_SOURCE_EXTENSIONS if it should be documented.\n`);
|
|
359
|
+
unscrapedDocBlocks.length = 0; // a walk reports its own findings, not the previous one's
|
|
360
|
+
}
|
|
287
361
|
return markdownFiles.sort(pinnedSort);
|
|
288
362
|
}
|
|
289
363
|
export function extractDocs(options) {
|
|
@@ -60,7 +60,14 @@ function entriesFromSrcScan() {
|
|
|
60
60
|
const entries = [];
|
|
61
61
|
let files;
|
|
62
62
|
try {
|
|
63
|
-
|
|
63
|
+
/*
|
|
64
|
+
`.tjs` too — and note `'x.tjs'.endsWith('.ts')` is FALSE, so the old test excluded it
|
|
65
|
+
silently rather than catching it by accident. Same root as tosijs-ui#108: a module ported
|
|
66
|
+
from `.ts` to `.tjs` dropped out of both the doc corpus and this index at once.
|
|
67
|
+
*/
|
|
68
|
+
files = fs
|
|
69
|
+
.readdirSync(SRC)
|
|
70
|
+
.filter((f) => f.endsWith('.ts') || f.endsWith('.tjs'));
|
|
64
71
|
}
|
|
65
72
|
catch {
|
|
66
73
|
return entries;
|