@wxxb789/anc 0.0.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/astro.config.mjs +255 -0
  4. package/bin/anc.mjs +798 -0
  5. package/package.json +93 -0
  6. package/public/_headers +45 -0
  7. package/public/favicon.ico +0 -0
  8. package/public/favicon.svg +9 -0
  9. package/public/fonts/Temml.woff2 +0 -0
  10. package/scripts/build-snapshot.js +69 -0
  11. package/scripts/build-wasm.js +67 -0
  12. package/scripts/build-workspace.js +50 -0
  13. package/scripts/copy-snapshot.js +48 -0
  14. package/scripts/copy-wasm.js +57 -0
  15. package/scripts/emit-redirects.js +64 -0
  16. package/scripts/generate-corpus.js +421 -0
  17. package/scripts/init-repository.js +249 -0
  18. package/scripts/load-config.js +975 -0
  19. package/scripts/markdown-to-artifact.js +1286 -0
  20. package/scripts/preview-site.js +364 -0
  21. package/scripts/publish-set-review.js +157 -0
  22. package/scripts/resolve-links.js +640 -0
  23. package/scripts/run-pagefind.js +125 -0
  24. package/scripts/scan-residue.js +1016 -0
  25. package/scripts/scan-secrets.js +268 -0
  26. package/scripts/seed-gitignore.js +410 -0
  27. package/scripts/snapshot-rows.js +205 -0
  28. package/scripts/validate-content.js +95 -0
  29. package/scripts/vendor-provenance.js +307 -0
  30. package/scripts/verify-output-inventory.js +307 -0
  31. package/scripts/write-report.js +351 -0
  32. package/scripts/write-snapshot.js +136 -0
  33. package/src/components/CollectionExplorer.astro +149 -0
  34. package/src/components/FacetIndex.astro +45 -0
  35. package/src/components/LinkedNotes.astro +94 -0
  36. package/src/components/NoteGraph.astro +434 -0
  37. package/src/components/NoteList.astro +54 -0
  38. package/src/components/SiteMetadata.astro +143 -0
  39. package/src/components/TableOfContents.astro +69 -0
  40. package/src/components/TagBrowser.astro +70 -0
  41. package/src/layouts/Layout.astro +305 -0
  42. package/src/lib/artifact-source.js +72 -0
  43. package/src/lib/build-bindings.js +11 -0
  44. package/src/lib/collection-navigation.js +145 -0
  45. package/src/lib/content.js +92 -0
  46. package/src/lib/diagram-mode.js +18 -0
  47. package/src/lib/graph-layout.js +219 -0
  48. package/src/lib/graph-selection.js +84 -0
  49. package/src/lib/graph.js +42 -0
  50. package/src/lib/link-resolution.js +389 -0
  51. package/src/lib/markdown.js +1469 -0
  52. package/src/lib/math-mode.js +31 -0
  53. package/src/lib/math-output.js +131 -0
  54. package/src/lib/math.js +165 -0
  55. package/src/lib/mermaid-environment.js +475 -0
  56. package/src/lib/mermaid-render.js +769 -0
  57. package/src/lib/preview-model.js +96 -0
  58. package/src/lib/relations.js +242 -0
  59. package/src/lib/rendered-marker.js +55 -0
  60. package/src/lib/route-path.js +192 -0
  61. package/src/lib/routes.js +342 -0
  62. package/src/lib/schema.js +578 -0
  63. package/src/lib/site.js +578 -0
  64. package/src/lib/snapshot-contract.js +216 -0
  65. package/src/lib/snapshot-operations.js +147 -0
  66. package/src/lib/snapshot-queries.js +216 -0
  67. package/src/lib/snapshot-reader.js +210 -0
  68. package/src/lib/snapshot.js +233 -0
  69. package/src/lib/sqlite-warning.js +22 -0
  70. package/src/lib/sqlite.js +14 -0
  71. package/src/lib/tag-browser-model.js +18 -0
  72. package/src/lib/translations.js +533 -0
  73. package/src/lib/wasm-asset.js +47 -0
  74. package/src/lib/worker-protocol.js +93 -0
  75. package/src/pages/404.astro +35 -0
  76. package/src/pages/about.astro +100 -0
  77. package/src/pages/collections/[slug].astro +36 -0
  78. package/src/pages/collections/index.astro +33 -0
  79. package/src/pages/graph.astro +64 -0
  80. package/src/pages/index.astro +33 -0
  81. package/src/pages/notes/[slug].astro +509 -0
  82. package/src/pages/privacy.astro +121 -0
  83. package/src/pages/private.astro +59 -0
  84. package/src/pages/recent.astro +33 -0
  85. package/src/pages/robots.txt.js +15 -0
  86. package/src/pages/rss.xml.js +21 -0
  87. package/src/pages/sitemap.xml.js +20 -0
  88. package/src/pages/tags/[tag].astro +56 -0
  89. package/src/pages/tags/index.astro +37 -0
  90. package/src/scripts/code-copy.js +45 -0
  91. package/src/scripts/diagram-disabled.js +16 -0
  92. package/src/scripts/diagram.js +322 -0
  93. package/src/scripts/graph-client.js +222 -0
  94. package/src/scripts/graph-draw.js +166 -0
  95. package/src/scripts/link-preview.js +334 -0
  96. package/src/scripts/math-disabled.js +18 -0
  97. package/src/scripts/math.js +133 -0
  98. package/src/scripts/preferences.js +123 -0
  99. package/src/scripts/search-dialog.js +651 -0
  100. package/src/scripts/snapshot-client.js +157 -0
  101. package/src/scripts/snapshot-worker.js +341 -0
  102. package/src/scripts/tag-browser.js +153 -0
  103. package/src/scripts/theme-init.js +35 -0
  104. package/src/scripts/toc-active.js +38 -0
  105. package/src/styles/code.css +152 -0
  106. package/src/styles/diagram.css +149 -0
  107. package/src/styles/global.css +1739 -0
  108. package/src/styles/graph.css +317 -0
  109. package/src/styles/math.css +376 -0
  110. package/src/styles/tokens.css +171 -0
  111. package/tsconfig.json +25 -0
@@ -0,0 +1,31 @@
1
+ export const MATH_MODE = 'client';
2
+ /*
3
+ * The costs carried by the selected client mode.
4
+ *
5
+ * Unlike Mermaid, temml needs no relaxation at all — measured at 0 CSP
6
+ * violations across every expression tried — and its single chunk carries no
7
+ * `[[`, no `data:` URL, and no drive-letter-shaped string, measured against the
8
+ * residue scan.
9
+ *
10
+ * What remains is the cost this selected mode moves onto the reader, recorded so
11
+ * the choice stays explicit rather than becoming an unexplained default:
12
+ *
13
+ * 1. **~116 KB gzip per math page**, against 0 in build-time mode. A page with
14
+ * no math pays nothing either way; the loader is gated on `hasMath`.
15
+ *
16
+ * **The received figure was ~59 KB and it is wrong**, which matters because
17
+ * it is the number the whole cost/benefit rests on. 59 KB is
18
+ * `temml.min.js`; the package's `exports` map sends `import('temml')` to the
19
+ * unminified `temml.cjs`/`temml.mjs`, and that is **115,541 B gzip**
20
+ * measured. Astro's bundler minifies what it emits, so the shipped figure
21
+ * should land nearer the 50 KB mark — but that is a prediction, and the
22
+ * number recorded here is the one that has actually been measured. Anyone
23
+ * flipping the mode should measure the emitted chunk rather than trust
24
+ * either.
25
+ * 2. **The TeX source is excluded from the search index**
26
+ * (`scripts/run-pagefind.ts`), so a reader cannot find a note by an
27
+ * expression in it. That is a real loss and it is stated where it is taken.
28
+ * 3. **A reader with JavaScript disabled sees the TeX source**, not typeset
29
+ * mathematics — the same bargain client diagrams make with their Mermaid
30
+ * source, and what the owner chose.
31
+ */
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The Temml post-processing both rendering modes need, with no Temml in it.
3
+ *
4
+ * `src/lib/math.ts` owns the build-time path and imports Temml, so a client
5
+ * runtime importing that module would pull Temml's server-side entry into the
6
+ * page bundle. These two functions are pure string transforms over Temml's
7
+ * output, so they live here and both modes call them.
8
+ *
9
+ * **Extracted because the client path was measured missing them, and the
10
+ * consequence was larger than any brief predicted.** Client mode without
11
+ * {@link extractStyles} lets every `style` attribute Temml writes reach a live
12
+ * DOM `setAttribute`, which is exactly what `style-src 'self'` blocks. Measured
13
+ * in Chromium under the shipped policy, violations per expression:
14
+ *
15
+ * | expression | violations |
16
+ * | --- | --- |
17
+ * | `x^2 + y^2 = z^2` | 0 |
18
+ * | `\begin{pmatrix} a & b \\ c & d \end{pmatrix}` | **4** |
19
+ * | `\begin{aligned}`, `\begin{array}` | **4** each |
20
+ * | `\boxed{E=mc^2}` | **1** |
21
+ * | `\pmb{x}` | 1 |
22
+ * | `\sideset{_a^b}{_c^d}\sum` | 2 |
23
+ *
24
+ * The received account was that `\pmb` and `\sideset` are the only violating
25
+ * commands, via three `MathNode.setAttribute` sites in `temml.mjs`. That is true
26
+ * of those three sites and false as a description of the whole: an ordinary
27
+ * matrix violates four times and goes nowhere near them. Running the same
28
+ * extraction the build-time path already runs removes the padding and border
29
+ * cases entirely and leaves the genuinely irreducible pair.
30
+ */
31
+ /**
32
+ * Inline declarations Temml emits that a class must reproduce, because dropping
33
+ * them changes the rendered geometry.
34
+ *
35
+ * The whole inline surface is 39 distinct declarations over a 77-expression
36
+ * corpus in both display and inline mode. Reproducing all 39 was rejected:
37
+ * measured in Chromium against Temml's own stylesheet, dropping each property
38
+ * group and re-measuring, only three groups move the box at all. The rest —
39
+ * `math-depth`, `width`, every `padding-left`/`padding-right` on `mtd`, and
40
+ * `color` — measured **0.0% deviation on all 24 test expressions**, because
41
+ * MathML Core's user-agent stylesheet already computes them. A class per
42
+ * declaration would have been 39 rules of which 36 changed nothing.
43
+ *
44
+ * What survives, with what it costs to drop it:
45
+ *
46
+ * | Declaration | Carrier | Dropping it |
47
+ * | --- | --- | --- |
48
+ * | `display:block math` | `math.tml-display` | up to 9% on a wide array |
49
+ * | `padding` / `padding-top` / `padding-bottom` | `mtd`, `mrow`, `mpadded` | up to 32% on `align`, `gather`, `bordermatrix` |
50
+ * | `border` / `border-right` / `border-bottom` | `mrow`, `mtd` | up to 8% on `\boxed`, and a lost rule in `array` |
51
+ *
52
+ * Each is reproduced by a rule in `src/styles/math.css` keyed on an element and
53
+ * a class this module writes. `display:block math` is additionally implied by
54
+ * `math.tml-display`, which Temml sets itself, and the `array` borders are
55
+ * structural rather than author-chosen.
56
+ *
57
+ * One value in the set is genuinely dynamic — `\raisebox`'s
58
+ * `padding: 2.8892pt 0 0 0`, whose length comes from the author — and it is
59
+ * *dropped*, not reproduced. The raise survives anyway, because Temml also
60
+ * emits `voffset`, a real MathML attribute that the sanitizer keeps and that
61
+ * does the same job. Nothing else in the set depends on author input.
62
+ */
63
+ export const MATH_STYLE_CLASSES = {
64
+ 'border:1px solid': 'math-boxed',
65
+ 'padding:3pt': 'math-boxed-pad',
66
+ 'border-right:0.06em solid': 'math-cell-rule-right',
67
+ 'border-bottom:0.06em solid': 'math-cell-rule-bottom',
68
+ 'padding:0': 'math-cell-tight',
69
+ 'padding-top:0': 'math-cell-tight-top',
70
+ 'padding-bottom:0': 'math-cell-tight-bottom',
71
+ };
72
+ /**
73
+ * Rewrite every inline `style` attribute into the classes above, dropping the
74
+ * declarations that measured no layout effect.
75
+ *
76
+ * Each declaration inside an attribute is looked up on its own, because Temml
77
+ * groups several onto one element: an `array` cell carries
78
+ * `border-bottom:0.06em solid;padding-left:0pt;padding-right:5.9776pt;
79
+ * border-right:0.06em solid` in a single `style`. A declaration the table does
80
+ * not name is dropped — which is what the sanitizer would have done anyway at
81
+ * build time, the difference being that here it is a decision with a measurement
82
+ * behind it rather than an accident of the allowlist. The `mtd` paddings are
83
+ * exactly that case: measured at 0.0% layout effect across 24 expressions,
84
+ * because MathML Core's user-agent stylesheet already supplies them.
85
+ *
86
+ * **In client mode the sanitizer is not there at all**, which is what makes this
87
+ * function load-bearing rather than tidy: the string goes into `innerHTML`, so
88
+ * an unextracted `style` becomes a real attribute and a real CSP violation.
89
+ */
90
+ export function extractStyles(html) {
91
+ return html.replace(/<([a-zA-Z0-9:-]+)((?:[^>"']|"[^"]*"|'[^']*')*?)\sstyle="([^"]*)"/g, (_match, tag, rest, css) => {
92
+ const classes = [];
93
+ for (const declaration of css.split(';')) {
94
+ const trimmed = declaration.trim();
95
+ if (trimmed === '')
96
+ continue;
97
+ const name = MATH_STYLE_CLASSES[trimmed];
98
+ if (name !== undefined && !classes.includes(name))
99
+ classes.push(name);
100
+ }
101
+ if (classes.length === 0)
102
+ return `<${tag}${rest}`;
103
+ const existing = /\sclass="([^"]*)"/.exec(rest);
104
+ if (existing) {
105
+ return `<${tag}${rest.replace(/\sclass="([^"]*)"/, ` class="$1 ${classes.join(' ')}"`)}`;
106
+ }
107
+ return `<${tag}${rest} class="${classes.join(' ')}"`;
108
+ });
109
+ }
110
+ /**
111
+ * `\rule` draws a bar whose colour Temml hardcodes as
112
+ * `<mspace mathbackground="black">`. On the dark palette that is a black bar on
113
+ * a `#0d1013` background — invisible, and invisible in a way no gate would see,
114
+ * because the element is present and correctly sized.
115
+ *
116
+ * `currentColor` is the fix and it is exact rather than approximate: MathML
117
+ * resolves it against the inherited text colour, which is `--color-text` in
118
+ * both themes, so the rule renders in the same ink as the expression around it.
119
+ * That is what `\rule` means — a bar drawn in the current pen — and it is what
120
+ * every other Temml border already does, via `border-color: currentColor` in
121
+ * Temml's own stylesheet.
122
+ *
123
+ * Narrow by construction: only `mathbackground`, only the literal `black`
124
+ * Temml writes. An author cannot reach this attribute — `\colorbox`, the one
125
+ * command that sets a background of its own choosing, is rejected by
126
+ * `src/lib/math.ts` at build time and cannot produce a colour Temml would put
127
+ * here.
128
+ */
129
+ export function currentColorRules(html) {
130
+ return html.replaceAll('<mspace mathbackground="black"', '<mspace mathbackground="currentColor"');
131
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * TeX to MathML at build time, with no client JavaScript.
3
+ *
4
+ * Owner decision 2 settles the engine: Temml, not KaTeX. Temml emits native
5
+ * MathML, which every current browser lays out itself — MathML Core has been
6
+ * Baseline widely available since January 2023 — so the page ships markup and a
7
+ * ~9 KB stylesheet instead of a 23 KB stylesheet, a webfont set, and a script.
8
+ * KaTeX's default HTML output additionally puts an inline `style` on nearly
9
+ * every span, which `style-src 'self'` blocks.
10
+ *
11
+ * Three properties are load-bearing:
12
+ *
13
+ * 1. **Zero inline styles reach the page.** Temml writes a small, closed set of
14
+ * `style` attributes. `sanitize-html` drops every one of them, so what has to
15
+ * be decided is which of them *matter*, not how to preserve them all. See
16
+ * {@link STYLE_CLASSES}.
17
+ * 2. **Rendering is deterministic.** Temml mints no ids, reads no clock, and
18
+ * calls no random source: the same TeX yields byte-identical MathML, verified
19
+ * across renders and across processes.
20
+ * 3. **A rejected expression fails loudly.** `throwOnError` is on, so a
21
+ * malformed expression aborts the build rather than shipping Temml's own
22
+ * error markup, which is a red `<mtext>` carrying an inline `color` that the
23
+ * sanitizer would then strip — leaving an unmarked, unreadable fragment.
24
+ */
25
+ import temml from 'temml';
26
+ import { currentColorRules, extractStyles, MATH_STYLE_CLASSES } from "./math-output.js";
27
+ export { MATH_STYLE_CLASSES };
28
+ /**
29
+ * TeX constructs rejected outright, each because it would put something in the
30
+ * document that the projection cannot vouch for.
31
+ *
32
+ * Temml already refuses `\href`, `\url`, `\includegraphics`, `\class`,
33
+ * `\cssId`, `\style`, and `\htmlData` under `trust: false` — verified, not
34
+ * assumed — so the only rejection this module has to make itself is colour.
35
+ *
36
+ * **`\textcolor`, `\color`, and `\colorbox` are rejected rather than mapped.**
37
+ * They are the one unbounded, author-chosen part of Temml's output: every other
38
+ * inline declaration comes from a closed set, and these produce an arbitrary
39
+ * `color:#rrggbb`. There is no correct thing to do with such a value on a site
40
+ * with a light and a dark palette. Keeping it fails contrast in one theme —
41
+ * `\textcolor{red}` on `#0d1013` is 3.9:1, below the 4.5:1 AA floor, and
42
+ * `\textcolor{yellow}` on `#fbfaf7` is 1.1:1 in the other direction. Mapping it
43
+ * onto a palette token silently renders something other than what the author
44
+ * wrote. Dropping it silently — which is what the sanitizer does today — makes
45
+ * the emphasis vanish with no signal, and colour was in all likelihood the only
46
+ * thing distinguishing that term.
47
+ *
48
+ * So the build fails and names the expression. An author who wants emphasis
49
+ * inside math has `\mathbf`, `\underline`, `\boxed`, and `\overbrace`, all of
50
+ * which survive both themes and are not colour-alone distinctions (WCAG 1.4.1).
51
+ * Requirements §15.2 permits exactly this: a construct that cannot be rendered
52
+ * safely fails export rather than degrading silently.
53
+ *
54
+ * **The list is a courtesy, not the guarantee.** It exists to give an author a
55
+ * message naming the command they wrote. What actually holds the property is
56
+ * {@link assertNoAuthoredColour}, which inspects the rendered output — because
57
+ * a name list cannot be complete, and was not: `\fcolorbox` is a fifth colour
58
+ * command that this list originally missed, and it was *worse* than the four
59
+ * here, because it emits `mathbackground="#FFFF00"` as an attribute rather than
60
+ * a style, so the extraction pass never saw it and the zero-inline-styles gate
61
+ * stayed green while an author-chosen yellow shipped at 1.1:1 on the light
62
+ * palette. A blocklist in a module whose own policy is allowlists was the bug.
63
+ */
64
+ const REJECTED_COMMANDS = [
65
+ '\\textcolor',
66
+ '\\color',
67
+ '\\colorbox',
68
+ '\\fcolorbox',
69
+ '\\pagecolor',
70
+ ];
71
+ /**
72
+ * MathML attributes that carry a colour, and the only value this projection
73
+ * permits in one.
74
+ *
75
+ * `currentColor` resolves against the inherited text colour, which is
76
+ * `--color-text` in both themes — so it is the one value that cannot fail
77
+ * contrast, because it *is* the colour the surrounding prose already uses.
78
+ * Anything else is a literal an author chose, and the whole argument above says
79
+ * such a literal cannot hold contrast in both palettes.
80
+ *
81
+ * Checked over the rendered output rather than the source, which is what makes
82
+ * it total: it does not matter which command produced the colour, whether that
83
+ * command is named in {@link REJECTED_COMMANDS}, or whether a future Temml
84
+ * version adds a sixth way to set one.
85
+ */
86
+ const COLOUR_ATTRIBUTES = ['mathbackground', 'mathcolor', 'color', 'background'];
87
+ function assertNoAuthoredColour(tex, html) {
88
+ for (const attribute of COLOUR_ATTRIBUTES) {
89
+ for (const [, value] of html.matchAll(new RegExp(`\\s${attribute}="([^"]*)"`, 'g'))) {
90
+ if (value !== 'currentColor') {
91
+ throw new MathRejectedError(tex, `the expression sets an author-chosen colour (${attribute}="${value}"), which cannot ` +
92
+ 'hold contrast in both themes. Use \\mathbf, \\underline, \\boxed, or \\overbrace, ' +
93
+ 'which survive both.');
94
+ }
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Thrown when an expression uses a construct this projection rejects.
100
+ *
101
+ * Fields are assigned in the body rather than declared as parameter
102
+ * properties: `node --experimental-strip-types` — which is how every script in
103
+ * `scripts/` runs — rejects that syntax outright, because erasing the types
104
+ * would change the runtime behaviour rather than only the annotations.
105
+ */
106
+ export class MathRejectedError extends Error {
107
+ tex;
108
+ reason;
109
+ constructor(tex, reason) {
110
+ super(`math rejected: ${reason}\n in: ${tex.trim().slice(0, 200)}`);
111
+ this.name = 'MathRejectedError';
112
+ this.tex = tex;
113
+ this.reason = reason;
114
+ }
115
+ }
116
+ /**
117
+ * One expression's MathML.
118
+ *
119
+ * @param tex The expression body, without delimiters.
120
+ * @param isDisplay Display math (`$$…$$`) rather than inline.
121
+ * @throws {MathRejectedError} when the expression uses a rejected construct or
122
+ * Temml cannot parse it. Both abort the build; see the note on `throwOnError`.
123
+ */
124
+ export function renderMath(tex, isDisplay) {
125
+ for (const command of REJECTED_COMMANDS) {
126
+ // Word-boundary on the command name, so `\colorbox` does not match inside
127
+ // `\colorboxes` and `\color` does not match inside `\colorbox` — the longer
128
+ // name is listed and would be reported under its own entry either way.
129
+ if (new RegExp(`${command.replace('\\', '\\\\')}(?![a-zA-Z])`).test(tex)) {
130
+ throw new MathRejectedError(tex, `${command} sets an author-chosen colour, which cannot hold contrast in both themes. ` +
131
+ 'Use \\mathbf, \\underline, \\boxed, or \\overbrace, which survive both.');
132
+ }
133
+ }
134
+ let html;
135
+ try {
136
+ html = temml.renderToString(tex, {
137
+ displayMode: isDisplay,
138
+ // A parse failure must stop the build. Temml's fallback is markup carrying
139
+ // an inline `color`, which the sanitizer strips — so the page would show
140
+ // the broken source with nothing marking it as broken.
141
+ throwOnError: true,
142
+ // Refuses `\href`, `\url`, `\includegraphics`, `\class`, `\cssId`,
143
+ // `\style`, and `\htmlData`: every command that would inject a URL, a
144
+ // class, or an attribute of the author's choosing.
145
+ trust: false,
146
+ // Bounds the two expansion vectors a hostile or looping macro would use.
147
+ // Temml's own defaults; restated because they are a security property
148
+ // rather than a rendering preference.
149
+ maxExpand: 1000,
150
+ maxSize: [500, 500],
151
+ // No `<annotation>` copy of the TeX source. It would double the byte cost
152
+ // of every expression and put a second copy of the text into the Pagefind
153
+ // index, where it reads as noise next to the rendered form.
154
+ annotate: false,
155
+ });
156
+ }
157
+ catch (error) {
158
+ throw new MathRejectedError(tex, error instanceof Error ? error.message.split('\n')[0] : String(error));
159
+ }
160
+ const extracted = currentColorRules(extractStyles(html));
161
+ // Last, and over the *output*: whatever produced a colour, and whichever
162
+ // command spelled it, it does not ship unless it is `currentColor`.
163
+ assertNoAuthoredColour(tex, extracted);
164
+ return extracted;
165
+ }