tosijs-ui 1.6.22 → 1.6.23
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 +61 -0
- package/dist/doc-browser.js +6 -3
- package/dist/doc-system/doc-title.d.ts +19 -0
- package/dist/doc-system/doc-title.js +51 -0
- package/dist/doc-system/site/check-examples-cli.d.ts +1 -0
- package/dist/doc-system/site/check-examples-cli.js +39 -0
- package/dist/doc-system/site/dev-server.d.ts +26 -0
- package/dist/doc-system/site/dev-server.js +442 -69
- package/dist/doc-system/site/generate-site.js +4 -6
- package/dist/doc-system/site/orchestrator.js +75 -6
- package/dist/doc-system/site/preflight.d.ts +152 -0
- package/dist/doc-system/site/preflight.js +401 -0
- package/dist/doc-system/site/site-config.d.ts +24 -0
- package/dist/iife.js +55 -55
- package/dist/iife.js.map +8 -7
- package/dist/live-example/component.d.ts +0 -1
- package/dist/live-example/component.js +8 -8
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.23
|
|
4
|
+
|
|
5
|
+
**The two biggest sources of ecosystem-wide friction — runaway dev servers and haltija
|
|
6
|
+
conflicts — fixed in the version people actually run.** If you use `tosijs-ui/site`, update.
|
|
7
|
+
|
|
8
|
+
### Machine-safety guards (a leaking/forgotten dev server took a machine down twice)
|
|
9
|
+
|
|
10
|
+
- **`killStrayServer` no longer `kill -9`s processes merely _connected_ to its port.** It freed
|
|
11
|
+
its port with `lsof -ti:PORT | xargs kill -9`, and `lsof -i:PORT` matches sockets whose local
|
|
12
|
+
**or remote** port is PORT — so it SIGKILLed every _client_ of that port, not just the
|
|
13
|
+
listener: the browser reading your page, an editor's language server, anything with a
|
|
14
|
+
connection. It now signals **only the process listening** on the port, only after confirming
|
|
15
|
+
it is a JS runtime, SIGTERM first and SIGKILL only as a fallback.
|
|
16
|
+
|
|
17
|
+
- **Machine-health preflight** before each build and at dev-server launch (and on a 60s tick):
|
|
18
|
+
refuses to add load to a machine that is already dying — a runaway dev server, a VM stall.
|
|
19
|
+
Names the offending PIDs, sizes, ages and project dirs. **Local LLM runtimes (LM Studio,
|
|
20
|
+
ollama, llama.cpp, mlx_lm, vLLM, …) are exempt** — their memory shrinks the dev budget rather
|
|
21
|
+
than tripping the guard, and a big _non-dev_ process (Docker, a JVM, a database) only warns,
|
|
22
|
+
never fails. It never kills anything; it declines to _start_. `preflight: 'fail' | 'warn' |
|
|
23
|
+
false` in the site config, or `DEV_SKIP_PREFLIGHT=1`; auto-downgraded to a warning in CI.
|
|
24
|
+
|
|
25
|
+
- **`idleTimeoutHours` (default 8)** — ⚠️ **behavior change:** the dev server now exits after 8
|
|
26
|
+
idle hours (no request, no rebuild). A forgotten dev server is not inert — it is a days-old
|
|
27
|
+
process still running the code it loaded at launch (updating the package does nothing for it).
|
|
28
|
+
`idleTimeoutHours: 0` disables it.
|
|
29
|
+
|
|
30
|
+
- **RSS ceiling** (`memoryLimitMb`) sampled every 60s, and a **rebuild-loop detector** that
|
|
31
|
+
stops a build which writes a file it also watches instead of spawning a bundler forever.
|
|
32
|
+
|
|
33
|
+
### haltija
|
|
34
|
+
|
|
35
|
+
- Spawned as a **pinned range (`haltija@^1.4.0`)**, overridable with `HALTIJA_VERSION`, instead
|
|
36
|
+
of a floating `@latest` fetched fresh on every adopter's machine. The 1.4.0 floor is
|
|
37
|
+
deliberate: it routes `hj` by working directory (so a project's dev server drives its own
|
|
38
|
+
browser, not whichever was focused), never overwrites a newer machine-wide `hj`, and exits
|
|
39
|
+
non-zero on a failed command — which the doc-test lane now checks instead of racing to a
|
|
40
|
+
timeout. Teardown kills only its own process tree, never a haltija you were already running.
|
|
41
|
+
|
|
42
|
+
### Also fixed
|
|
43
|
+
|
|
44
|
+
- **Builds again against tosijs 1.6.9.** `<tosi-example>` detected hydration with a
|
|
45
|
+
`try { this.parts.js } catch` probe, which before tosijs 1.6.9 permanently poisoned the
|
|
46
|
+
`parts` proxy on the pre-hydration read it performed (it could blank the whole doc page), and
|
|
47
|
+
whose `private get hydrated()` collided with 1.6.9's new public `hydrated` (a `tsc` error, so
|
|
48
|
+
the package could not build against current tosijs). The probe is gone — it uses the base
|
|
49
|
+
class's `this.hydrated` now — and the tosijs floor moves to **`^1.6.9`**
|
|
50
|
+
([tosijs#13](https://github.com/tonioloewald/tosijs/issues/13)).
|
|
51
|
+
|
|
52
|
+
- **The doc-browser no longer double-suffixes the page `<title>`.** On path routing it re-derived
|
|
53
|
+
the title on hydration, ignoring `headTitle` and re-appending the project name to a title that
|
|
54
|
+
already contained it — so the home page flipped from its real title to `tosijs-ui — tosijs-ui`
|
|
55
|
+
the moment the bundle loaded. The static generator and the runtime now share **one**
|
|
56
|
+
`pageTitle` rule (case-insensitive, both-directions containment check; whitespace-only titles
|
|
57
|
+
and project names handled). Fixes [#6](https://github.com/tonioloewald/tosijs-ui/issues/6).
|
|
58
|
+
|
|
59
|
+
- **Two more per-rebuild build leaks moved to child processes** (the dev server lives for days):
|
|
60
|
+
the live-example **check** (`new AsyncFunction` per block + a `Bun.Transpiler` per `ts` block)
|
|
61
|
+
and the bundle **gzip** (native zlib). Both now run in a child whose memory the OS reclaims on
|
|
62
|
+
exit; the gzip stays zlib-in-a-child (not the `gzip` CLI) so the reported size doesn't drift.
|
|
63
|
+
|
|
3
64
|
## 1.6.22
|
|
4
65
|
|
|
5
66
|
### Fixed
|
package/dist/doc-browser.js
CHANGED
|
@@ -155,6 +155,7 @@ import { elements, vars, varDefault, bindings, touch, getListItem, debounce, tos
|
|
|
155
155
|
import { buildSlugMap, pathForSlug, filenameForPath } from './doc-system/routing';
|
|
156
156
|
import { buildNavTree } from './doc-system/nav-tree';
|
|
157
157
|
import { renderDocMarkdown } from './doc-system/render';
|
|
158
|
+
import { pageTitle } from './doc-system/doc-title';
|
|
158
159
|
import { LiveExample, testManager } from './live-example';
|
|
159
160
|
import { tosiSidenav, TosiSidenav } from './side-nav';
|
|
160
161
|
import { icons } from './icons';
|
|
@@ -613,9 +614,11 @@ export function createDocBrowser(options) {
|
|
|
613
614
|
LiveExample.insertExamples(docContent, context, doc.path || undefined);
|
|
614
615
|
scrollToHashExample();
|
|
615
616
|
if (routing === 'path') {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
617
|
+
// The SAME rule the static generator used for this page's <head> (doc-title.ts).
|
|
618
|
+
// This used to re-derive it — ignoring `headTitle` and re-suffixing a title that
|
|
619
|
+
// already ended in the project name — so the home page's title flipped to
|
|
620
|
+
// "tosijs-ui — tosijs-ui" the moment the bundle loaded (issue #6).
|
|
621
|
+
document.title = pageTitle(doc, projectName);
|
|
619
622
|
}
|
|
620
623
|
};
|
|
621
624
|
// Always resolve to the RAW doc from the original array — docs reached via the
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The fields of a Doc that bear on its title. Structural, so both callers fit. */
|
|
2
|
+
export interface TitledDoc {
|
|
3
|
+
title: string;
|
|
4
|
+
/** verbatim <title>, no project suffix — a per-doc override */
|
|
5
|
+
headTitle?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* `headTitle` wins verbatim. Otherwise suffix the project name — but only when the two
|
|
9
|
+
* are genuinely distinct.
|
|
10
|
+
*
|
|
11
|
+
* "Distinct" means **neither string contains the other, compared case-insensitively.**
|
|
12
|
+
* A one-way, case-sensitive `title.includes(projectName)` check missed real cases: a
|
|
13
|
+
* project titled `Tosijs-UI` against a doc titled `tosijs-ui` (case), and a doc whose
|
|
14
|
+
* title is a *substring* of the project name (the containment runs the other way). Any
|
|
15
|
+
* of those, suffixed, reads as redundant — "tosijs-ui — tosijs-ui" being the one that
|
|
16
|
+
* shipped (issue #6). When one already contains the other, the doc's own title is the
|
|
17
|
+
* more specific label, so use it alone.
|
|
18
|
+
*/
|
|
19
|
+
export declare function pageTitle(doc: TitledDoc, projectName?: string): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The ONE rule for a doc page's <title>.
|
|
3
|
+
|
|
4
|
+
It has to be one rule, because it is applied twice — once by the static generator into
|
|
5
|
+
the pre-rendered `<head>`, and once by the doc-browser when it hydrates. Two
|
|
6
|
+
independently-written copies drifted, and the title became the one thing on the page
|
|
7
|
+
that visibly MOVED on hydration:
|
|
8
|
+
|
|
9
|
+
JS off → "tosijs-ui — robust, dependency-free web components" (headTitle)
|
|
10
|
+
hydrated → "tosijs-ui — tosijs-ui" (!)
|
|
11
|
+
|
|
12
|
+
...because the runtime copy ignored `headTitle` and re-suffixed a title that already
|
|
13
|
+
ended in the project name. That is GitHub issue #6, and it was live on our own home
|
|
14
|
+
page while this release's whole thesis was "hydration is purely additive, nothing
|
|
15
|
+
moves".
|
|
16
|
+
|
|
17
|
+
Shared by `site/generate-site.ts` (build) and `doc-browser.ts` (runtime). Pure — no DOM,
|
|
18
|
+
no Bun — so both can import it.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* `headTitle` wins verbatim. Otherwise suffix the project name — but only when the two
|
|
22
|
+
* are genuinely distinct.
|
|
23
|
+
*
|
|
24
|
+
* "Distinct" means **neither string contains the other, compared case-insensitively.**
|
|
25
|
+
* A one-way, case-sensitive `title.includes(projectName)` check missed real cases: a
|
|
26
|
+
* project titled `Tosijs-UI` against a doc titled `tosijs-ui` (case), and a doc whose
|
|
27
|
+
* title is a *substring* of the project name (the containment runs the other way). Any
|
|
28
|
+
* of those, suffixed, reads as redundant — "tosijs-ui — tosijs-ui" being the one that
|
|
29
|
+
* shipped (issue #6). When one already contains the other, the doc's own title is the
|
|
30
|
+
* more specific label, so use it alone.
|
|
31
|
+
*/
|
|
32
|
+
export function pageTitle(doc, projectName) {
|
|
33
|
+
// A whitespace-only `headTitle` is not an override, it's a blank <title> — trim it
|
|
34
|
+
// and fall through. Trimming also cleans up an accidentally-padded one ("verbatim"
|
|
35
|
+
// means "no project suffix", not "keep your stray whitespace").
|
|
36
|
+
const headTitle = doc.headTitle?.trim();
|
|
37
|
+
if (headTitle)
|
|
38
|
+
return headTitle;
|
|
39
|
+
// Trim first: a whitespace-only name ('' or ' ') is "no project name", not a real
|
|
40
|
+
// one. Untrimmed, ' ' is truthy and slips through — and worse, `title.includes(' ')`
|
|
41
|
+
// is true for any multi-word title, so it would silently suppress a suffix that
|
|
42
|
+
// should appear. Trimming also strips a padded 'tosijs-ui ' down to the real name.
|
|
43
|
+
const project = projectName?.trim() ?? '';
|
|
44
|
+
if (!project)
|
|
45
|
+
return doc.title;
|
|
46
|
+
const t = doc.title.toLowerCase();
|
|
47
|
+
const p = project.toLowerCase();
|
|
48
|
+
if (t.includes(p) || p.includes(t))
|
|
49
|
+
return doc.title;
|
|
50
|
+
return `${doc.title} — ${project}`;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Live-example build check — the subprocess entry point for checkExamples().
|
|
3
|
+
|
|
4
|
+
Why a child process, when this is "just" compiling some strings:
|
|
5
|
+
|
|
6
|
+
checkExamples() compiles EVERY executable block in the corpus (83 of them here) with
|
|
7
|
+
`new AsyncFunction(js)` on EVERY dev rebuild, and — for `ts` blocks — runs them
|
|
8
|
+
through a `Bun.Transpiler`. Both retain memory the JS heap never shows you:
|
|
9
|
+
|
|
10
|
+
- JSC caches compiled code, KEYED BY SOURCE TEXT. That last part is the trap: replay
|
|
11
|
+
the same corpus and RSS plateaus (the cache dedups), so a leak test on an unchanged
|
|
12
|
+
corpus reports "flat" and lies. But a dev server only rebuilds BECAUSE A FILE
|
|
13
|
+
CHANGED — the source is fresh every time, so nothing dedups and it never plateaus.
|
|
14
|
+
Measured with fresh sources: +7.1MB over 40 rebuilds, still climbing.
|
|
15
|
+
- `Bun.Transpiler` strands ~40KB per construction (oven-sh/bun#34053).
|
|
16
|
+
|
|
17
|
+
In a process that lives for days across thousands of rebuilds, that is unbounded. The
|
|
18
|
+
child hands all of it back to the OS on exit. Same reasoning — and the same fix — as
|
|
19
|
+
the bundle step, the ePub step, and generate-css; see the Bun.build note in
|
|
20
|
+
orchestrator.ts.
|
|
21
|
+
|
|
22
|
+
bun check-examples-cli.ts <docs.json> [contextKey ...]
|
|
23
|
+
→ writes ExampleProblem[] as JSON to stdout; exit 0 whether or not problems were
|
|
24
|
+
found (a problem is data, not a crash — only a real failure exits non-zero).
|
|
25
|
+
|
|
26
|
+
Build-time only. Never import this from browser code.
|
|
27
|
+
*/
|
|
28
|
+
import { checkExamples } from './check-examples';
|
|
29
|
+
const docsJson = process.argv[2];
|
|
30
|
+
if (!docsJson) {
|
|
31
|
+
console.error('check-examples-cli: expected a docs.json path');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const contextKeys = process.argv.slice(3);
|
|
35
|
+
const corpus = JSON.parse(await Bun.file(docsJson).text());
|
|
36
|
+
const problems = await checkExamples(corpus, contextKeys.length ? { contextKeys } : {});
|
|
37
|
+
// stdout is the channel — the parent parses this. Anything else this process prints
|
|
38
|
+
// (warnings from the transform, say) goes to stderr so it can't corrupt the payload.
|
|
39
|
+
process.stdout.write(JSON.stringify(problems));
|
|
@@ -2,6 +2,32 @@ import type { SiteConfig } from './site-config';
|
|
|
2
2
|
declare global {
|
|
3
3
|
var Bun: any;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the idle-exit timeout to milliseconds (0 = disabled).
|
|
7
|
+
*
|
|
8
|
+
* Env wins over config, config over the default. An unparseable value falls back
|
|
9
|
+
* to the default rather than to 0: a typo'd `DEV_IDLE_TIMEOUT_HOURS=8h` must not
|
|
10
|
+
* silently turn the guard OFF — that is the exact failure it exists to prevent.
|
|
11
|
+
* Only an explicit non-positive number disables it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveIdleMs(configHours: number | undefined, envHours: string | undefined): number;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the RSS ceiling in MB (0 = disabled).
|
|
16
|
+
*
|
|
17
|
+
* Same rule as `resolveIdleMs`, and it was NOT being applied: the ceiling was read as
|
|
18
|
+
* `Number(env ?? config ?? 4096)`, which fails in both directions at once —
|
|
19
|
+
*
|
|
20
|
+
* DEV_MEMORY_LIMIT_MB='' → `??` passes '' through → Number('') === 0 → the ceiling
|
|
21
|
+
* is ZERO, so `rss >= limit` is true on the first sample
|
|
22
|
+
* and the dev server kills itself on every rebuild.
|
|
23
|
+
* DEV_MEMORY_LIMIT_MB=4gb → NaN → every `>=` comparison is false → the guard is
|
|
24
|
+
* silently OFF, on the machine of someone who was
|
|
25
|
+
* explicitly trying to configure it.
|
|
26
|
+
*
|
|
27
|
+
* An empty env var is *unset*, not zero. Garbage falls back to the default, never to
|
|
28
|
+
* off. Only an explicit non-positive number disables the ceiling.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveLimitMb(configMb: number | undefined, envMb: string | undefined): number;
|
|
5
31
|
export declare function devServer(config: SiteConfig, opts?: {
|
|
6
32
|
test?: boolean;
|
|
7
33
|
build?: () => unknown | Promise<unknown>;
|