OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      f64ff8a24cb9
DATE      2026-07-18
SUBJECT   web: purge placeholder content, empty-safe status, fixture-based graph tests
FILES     16 CHANGED
HASH      f64ff8a24cb9
DATE      2026-07-18
SUBJECT   web: purge placeholder content,
          empty-safe status, fixture-based graph
          tests
FILES     16 CHANGED
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/authoring/wikilinks.md b/oo
knet-design/src/content/docs/ooknet-org/authoring/wikilinks.md
deleted file mode 100644
index b62c3c8..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/authoring/wikilinks.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "Wikilinks"
-order: 2
----
-
-# Syntax
-
-| FORM                             | RESOLVES BY                    |
-| -------------------------------- | ------------------------------ |
-| `[[Style Sheet]]`                | title, case-insensitive        |
-| `[[graphs/entropy-bounds]]`      | file id (path in collection)   |
-| `[[Style Sheet\|see the sheet]]` | alias - target before the pipe |
-
-Live: [[Entropy Bounds in Sparse Graphs]] by title,
-[[ooknet-org/style-sheet]] by id,
-[[Entropy Bounds in Sparse Graphs|aliased to this]] with an alias.
-
-# Resolution
-
-The map is built once per build by scanning notes, KB, and docs
-frontmatter. Every published page in every collection is indexed under
-its id and its title, so the three collections cite each other with
-the same syntax. Targets are trimmed and matched case-insensitively.
-
-# Dead links
-
-An unresolved target renders soft with a dashed underline - like
-[[A NOTE THAT DOES NOT EXIST]] - rather than failing the build. Hover
-shows the unresolved target. This is deliberate: drafts reference
-future pages, and the wiki tradition is to show the gap.
-
-# Where they work
-
-Anywhere markdown renders: notes, KB, docs, and any prose the pipeline
-touches. They don't fire inside code fences or existing links.
-
-# Referenced by
-
-The graph runs both ways. Every resolved wikilink also feeds a
-REFERENCED BY index on the target page - the pages that cite this one,
-listed above the footer. Dead links, drafts, and self-references stay
-out of the index, and literal syntax in code doesn't count as a
-citation. Nothing to maintain: link a page and both directions appear
-at the next build.

diff --git a/ooknet-design/src/content/docs/
ooknet-org/authoring/wikilinks.md b/ooknet-d
esign/src/content/docs/ooknet-org/authoring/
wikilinks.md
deleted file mode 100644
index b62c3c8..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/authoring/wikilinks.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "Wikilinks"
-order: 2
----
-
-# Syntax
-
-| FORM                             | RESOLV
ES BY                    |
-| -------------------------------- | ------
------------------------ |
-| `[[Style Sheet]]`                | title,
 case-insensitive        |
-| `[[graphs/entropy-bounds]]`      | file i
d (path in collection)   |
-| `[[Style Sheet\|see the sheet]]` | alias 
- target before the pipe |
-
-Live: [[Entropy Bounds in Sparse Graphs]] b
y title,
-[[ooknet-org/style-sheet]] by id,
-[[Entropy Bounds in Sparse Graphs|aliased t
o this]] with an alias.
-
-# Resolution
-
-The map is built once per build by scanning
 notes, KB, and docs
-frontmatter. Every published page in every 
collection is indexed under
-its id and its title, so the three collecti
ons cite each other with
-the same syntax. Targets are trimmed and ma
tched case-insensitively.
-
-# Dead links
-
-An unresolved target renders soft with a da
shed underline - like
-[[A NOTE THAT DOES NOT EXIST]] - rather tha
n failing the build. Hover
-shows the unresolved target. This is delibe
rate: drafts reference
-future pages, and the wiki tradition is to 
show the gap.
-
-# Where they work
-
-Anywhere markdown renders: notes, KB, docs,
 and any prose the pipeline
-touches. They don't fire inside code fences
 or existing links.
-
-# Referenced by
-
-The graph runs both ways. Every resolved wi
kilink also feeds a
-REFERENCED BY index on the target page - th
e pages that cite this one,
-listed above the footer. Dead links, drafts
, and self-references stay
-out of the index, and literal syntax in cod
e doesn't count as a
-citation. Nothing to maintain: link a page 
and both directions appear
-at the next build.
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/authoring/writing-content.m
d b/ooknet-design/src/content/docs/ooknet-org/authoring/writing-content.md
deleted file mode 100644
index 7b93cf0..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/authoring/writing-content.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: "Writing content"
-order: 1
----
-
-# Three collections
-
-Written content lives in `src/content/` in three collections, each
-with its own register:
-
-| COLLECTION | WHAT GOES THERE                  | SPLIT BY |
-| ---------- | -------------------------------- | -------- |
-| `notes/`   | personal writing, study notes    | subject  |
-| `kb/`      | vendor docs and guides, credited | topic    |
-| `docs/`    | project documentation            | project  |
-
-The directory is the taxonomy: `notes/nix/flakes.md` files a note
-under the NIX subject, `docs/wowsim-stats/api.md` documents the
-wowsim-stats project. No frontmatter field repeats what the path
-already says.
-
-# Notes
-
-```yaml
-title: "ENTROPY BOUNDS IN SPARSE GRAPHS"
-published: "2026-04-19"
-status: "PEER-CHECKED [PASS]"
-tags: ["entropy", "sparse-graphs"]
-```
-
-Only `title` and `published` are required.
-
-# Knowledge base
-
-KB entries are copies of external material and always carry the
-credit:
-
-```yaml
-title: "COLLECT DATA TO ANALYZE PERFORMANCE SCENARIOS"
-source: "MICROSOFT LEARN"
-url: "https://learn.microsoft.com/..."
-retrieved: "2026-04-25"
-tags: ["windows", "performance"]
-```
-
-`source` is the vendor; `url` and `retrieved` say where and when the
-copy was taken.
-
-# Cross-references
-
-All three collections share one wikilink map - a note can cite a KB
-entry, a doc can cite a note. See [[Wikilinks]] for the syntax. Every
-resolved link also feeds the REFERENCED BY index on the target page.
-
-Set `draft: true` in any collection to keep a page out of the index,
-the search, and the wikilink map.

diff --git a/ooknet-design/src/content/docs/
ooknet-org/authoring/writing-content.md b/oo
knet-design/src/content/docs/ooknet-org/auth
oring/writing-content.md
deleted file mode 100644
index 7b93cf0..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/authoring/writing-content.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: "Writing content"
-order: 1
----
-
-# Three collections
-
-Written content lives in `src/content/` in 
three collections, each
-with its own register:
-
-| COLLECTION | WHAT GOES THERE             
     | SPLIT BY |
-| ---------- | ----------------------------
---- | -------- |
-| `notes/`   | personal writing, study note
s    | subject  |
-| `kb/`      | vendor docs and guides, cred
ited | topic    |
-| `docs/`    | project documentation       
     | project  |
-
-The directory is the taxonomy: `notes/nix/f
lakes.md` files a note
-under the NIX subject, `docs/wowsim-stats/a
pi.md` documents the
-wowsim-stats project. No frontmatter field 
repeats what the path
-already says.
-
-# Notes
-
-```yaml
-title: "ENTROPY BOUNDS IN SPARSE GRAPHS"
-published: "2026-04-19"
-status: "PEER-CHECKED [PASS]"
-tags: ["entropy", "sparse-graphs"]
-```
-
-Only `title` and `published` are required.
-
-# Knowledge base
-
-KB entries are copies of external material 
and always carry the
-credit:
-
-```yaml
-title: "COLLECT DATA TO ANALYZE PERFORMANCE
 SCENARIOS"
-source: "MICROSOFT LEARN"
-url: "https://learn.microsoft.com/..."
-retrieved: "2026-04-25"
-tags: ["windows", "performance"]
-```
-
-`source` is the vendor; `url` and `retrieve
d` say where and when the
-copy was taken.
-
-# Cross-references
-
-All three collections share one wikilink ma
p - a note can cite a KB
-entry, a doc can cite a note. See [[Wikilin
ks]] for the syntax. Every
-resolved link also feeds the REFERENCED BY 
index on the target page.
-
-Set `draft: true` in any collection to keep
 a page out of the index,
-the search, and the wikilink map.
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/design/components.md b/ookn
et-design/src/content/docs/ooknet-org/design/components.md
deleted file mode 100644
index d42413f..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/design/components.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: "Component library"
-order: 3
----
-
-# Two kinds of component
-
-**Static** components are pure builders: a `.ts` function takes a
-width and returns a string, unit-tested, rendered at both frame widths
-through `Frame`. Tables, trees, timelines, gauges, calendars.
-
-**Interactive** components render once and wear ASCII chrome over
-native elements - a checkbox behind `[x]`, a range input over
-`├───█───┤`. They can't dual-render: duplicating an input duplicates
-its name and splits state across variants.
-
-# Conventions
-
-- one directory per component: `.astro`, `.ts`, optional `.scss` and `.test.ts`
-- every frame string passes `checkGrid` before it ships
-- motion is discrete and glyph-stepped, never a CSS fade
-- the live inventory renders on the [components page](/components)
-
-# Where to look first
-
-`Frame.astro` is the bridge between builders and the page. `Pre.astro`
-is the strict grid. Copy an existing component's directory and follow
-its shape.

diff --git a/ooknet-design/src/content/docs/
ooknet-org/design/components.md b/ooknet-des
ign/src/content/docs/ooknet-org/design/compo
nents.md
deleted file mode 100644
index d42413f..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/design/components.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: "Component library"
-order: 3
----
-
-# Two kinds of component
-
-**Static** components are pure builders: a 
`.ts` function takes a
-width and returns a string, unit-tested, re
ndered at both frame widths
-through `Frame`. Tables, trees, timelines, 
gauges, calendars.
-
-**Interactive** components render once and 
wear ASCII chrome over
-native elements - a checkbox behind `[x]`, 
a range input over
-`├───█───┤`. They can't dual-render: duplic
ating an input duplicates
-its name and splits state across variants.
-
-# Conventions
-
-- one directory per component: `.astro`, `.
ts`, optional `.scss` and `.test.ts`
-- every frame string passes `checkGrid` bef
ore it ships
-- motion is discrete and glyph-stepped, nev
er a CSS fade
-- the live inventory renders on the [compon
ents page](/components)
-
-# Where to look first
-
-`Frame.astro` is the bridge between builder
s and the page. `Pre.astro`
-is the strict grid. Copy an existing compon
ent's directory and follow
-its shape.
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/design/frames.md b/ooknet-d
esign/src/content/docs/ooknet-org/design/frames.md
deleted file mode 100644
index 71dad8f..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/design/frames.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: "Frames & the grid"
-order: 1
----
-
-# The grid is the contract
-
-Every visual element on this site is text on a monospace grid. The
-frame is 86 cells wide on desktop and 48 on mobile; components render
-both variants at build time and CSS shows one per viewport.
-
-| CONSTANT       | CELLS | WHERE                |
-| -------------- | ----: | -------------------- |
-| FRAME_W        |    86 | src/lib/config.ts    |
-| MOBILE_FRAME_W |    48 | src/lib/config.ts    |
-
-# checkGrid
-
-Nothing ships that would bend a border. Every frame passes through
-`checkGrid`, which fails the build if a line overflows its width or
-uses a glyph the frame font doesn't ship at one cell wide:
-
-```ts
-export function checkGrid(text: string, maxW: number, ctx: string): string {
-  // throws on overflow or off-grid codepoints
-}
-```
-
-The full glyph policy lives in [[Style Sheet]] - the doc that
-exercises every construct the pipeline renders.
-
-# Fonts
-
-Prose sets in IBM Plex Mono; frames set in Cascadia Mono, whose
-box-drawing glyphs are uniformly one cell. Never mix them on the same
-line of a frame.

diff --git a/ooknet-design/src/content/docs/
ooknet-org/design/frames.md b/ooknet-design/
src/content/docs/ooknet-org/design/frames.md
deleted file mode 100644
index 71dad8f..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/design/frames.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: "Frames & the grid"
-order: 1
----
-
-# The grid is the contract
-
-Every visual element on this site is text o
n a monospace grid. The
-frame is 86 cells wide on desktop and 48 on
 mobile; components render
-both variants at build time and CSS shows o
ne per viewport.
-
-| CONSTANT       | CELLS | WHERE           
     |
-| -------------- | ----: | ----------------
---- |
-| FRAME_W        |    86 | src/lib/config.t
s    |
-| MOBILE_FRAME_W |    48 | src/lib/config.t
s    |
-
-# checkGrid
-
-Nothing ships that would bend a border. Eve
ry frame passes through
-`checkGrid`, which fails the build if a lin
e overflows its width or
-uses a glyph the frame font doesn't ship at
 one cell wide:
-
-```ts
-export function checkGrid(text: string, max
W: number, ctx: string): string {
-  // throws on overflow or off-grid codepoi
nts
-}
-```
-
-The full glyph policy lives in [[Style Shee
t]] - the doc that
-exercises every construct the pipeline rend
ers.
-
-# Fonts
-
-Prose sets in IBM Plex Mono; frames set in 
Cascadia Mono, whose
-box-drawing glyphs are uniformly one cell. 
Never mix them on the same
-line of a frame.
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/design/pipeline.md b/ooknet
-design/src/content/docs/ooknet-org/design/pipeline.md
deleted file mode 100644
index b84668f..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/design/pipeline.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: "Markdown pipeline"
-order: 2
----
-
-# One source, ASCII out
-
-Notes and docs are plain markdown - previewable in Obsidian and
-GitHub - transformed at build time by `rehype-ascii`:
-
-| CONSTRUCT      | BECOMES                                  |
-| -------------- | ---------------------------------------- |
-| fenced code    | framed box with [ COPY ]                 |
-| table          | ASCII table + sr-only semantic table     |
-| [!NOTE] quote  | labeled callout                          |
-| lone image     | numbered figure                          |
-| diagram fence  | verbatim ASCII art as a figure           |
-| mermaid fence  | generated diagram as a figure            |
-| diff fence     | framed diff, adds bold, deletions soft   |
-
-# Diagrams
-
-A `mermaid` fence renders linear chains through the house layout and
-delegates branching graphs to mermaid-ascii. A `diagram` fence keeps
-hand-drawn art verbatim. Both take a `:: CAPTION` first line.
-
-# Wikilinks
-
-`[[target]]` resolves against notes and docs at build time - see
-[[Wikilinks]] for the syntax.

diff --git a/ooknet-design/src/content/docs/
ooknet-org/design/pipeline.md b/ooknet-desig
n/src/content/docs/ooknet-org/design/pipelin
e.md
deleted file mode 100644
index b84668f..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/design/pipeline.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: "Markdown pipeline"
-order: 2
----
-
-# One source, ASCII out
-
-Notes and docs are plain markdown - preview
able in Obsidian and
-GitHub - transformed at build time by `rehy
pe-ascii`:
-
-| CONSTRUCT      | BECOMES                 
                 |
-| -------------- | ------------------------
---------------- |
-| fenced code    | framed box with [ COPY ]
                 |
-| table          | ASCII table + sr-only se
mantic table     |
-| [!NOTE] quote  | labeled callout         
                 |
-| lone image     | numbered figure         
                 |
-| diagram fence  | verbatim ASCII art as a 
figure           |
-| mermaid fence  | generated diagram as a f
igure            |
-| diff fence     | framed diff, adds bold, 
deletions soft   |
-
-# Diagrams
-
-A `mermaid` fence renders linear chains thr
ough the house layout and
-delegates branching graphs to mermaid-ascii
. A `diagram` fence keeps
-hand-drawn art verbatim. Both take a `:: CA
PTION` first line.
-
-# Wikilinks
-
-`[[target]]` resolves against notes and doc
s at build time - see
-[[Wikilinks]] for the syntax.
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/overview.md b/ooknet-design
/src/content/docs/ooknet-org/overview.md
deleted file mode 100644
index ff3744b..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/overview.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: "Overview"
-order: 0
----
-
-# ooknet.org
-
-This site: a text-mode knowledge base rendered entirely as ASCII on a
-strict monospace grid. Notes, a credited knowledge base, and project
-documentation, cross-linked by a build-time wiki graph.
-
-The design system behind it lives in these docs - start with
-[[ooknet-org/design/frames]] for how the grid works, or the
-[[Style Sheet]] to see every construct the pipeline renders.

diff --git a/ooknet-design/src/content/docs/
ooknet-org/overview.md b/ooknet-design/src/c
ontent/docs/ooknet-org/overview.md
deleted file mode 100644
index ff3744b..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/overview.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: "Overview"
-order: 0
----
-
-# ooknet.org
-
-This site: a text-mode knowledge base rende
red entirely as ASCII on a
-strict monospace grid. Notes, a credited kn
owledge base, and project
-documentation, cross-linked by a build-time
 wiki graph.
-
-The design system behind it lives in these 
docs - start with
-[[ooknet-org/design/frames]] for how the gr
id works, or the
-[[Style Sheet]] to see every construct the 
pipeline renders.
 

diff --git a/ooknet-design/src/content/docs/ooknet-org/style-sheet.md b/ooknet-des
ign/src/content/docs/ooknet-org/style-sheet.md
deleted file mode 100644
index 9fd4c05..0000000
--- a/ooknet-design/src/content/docs/ooknet-org/style-sheet.md
+++ /dev/null
@@ -1,124 +0,0 @@
----
-title: "Style sheet"
-description: "Exercises every markdown construct the ASCII pipeline renders."
-order: 99
----
-
-# Purpose
-
-This sheet exercises every markdown construct the ASCII pipeline
-renders. If a change to the design system breaks layout, it should be
-visible on this page first.
-
-# Tables
-
-| GLYPH SET | CELLS | STATUS   |
-| --------- | ----: | -------- |
-| single    |    86 | ACTIVE   |
-| double    |    48 | RESERVED |
-| heavy     |    86 | DROPPED  |
-
-A wider table that must shrink and wrap on the narrow frame:
-
-| COMPONENT | RESPONSIBILITY                            | NOTES                  
      |
-| --------- | ----------------------------------------- | -----------------------
----- |
-| Frame     | renders a build function at both widths   | static content only    
      |
-| TextField | input seated on an underscore placeholder | occupies exact cells   
      |
-| Select    | native dropdown inside bracket chrome     | arrow glyph must be on-
grid  |
-
-# Callouts
-
-> [!NOTE]
-> The frame font must ship every glyph the renderer emits. Unknown
-> codepoints fall back to a wider face and break the cell grid.
-
-> [!WARNING]
-> Interactive elements cannot dual-render: duplicating an input
-> duplicates its name and splits radio state across variants.
-
-> A plain blockquote is unaffected by the callout transform and keeps
-> its italic rail styling.
-
-# Figures
-
-![Dual-width frame rendering](/figures/n0-frame-schematic.svg)
-
-# Diagrams
-
-A `diagram` fence renders hand-drawn ASCII art as a numbered figure.
-The first line may set a caption with `::`.
-
-```diagram
-:: THE SAME BUILD FUNCTION AT BOTH WIDTHS
-                ┌──────────────┐
-                │ build(width) │
-                └──────┬───────┘
-          ┌────────────┴────────────┐
-   ┌──────┴───────┐        ┌────────┴───────┐
-   │ FRAME_W = 86 │        │ MOBILE_W = 48  │
-   │ .frame-wide  │        │ .frame-narrow  │
-   └──────────────┘        └────────────────┘
-```
-
-A `mermaid` fence renders at build time - and previews live in
-Obsidian and GitHub, since it's plain mermaid. Linear chains lay out
-through the house renderer; chains that fit the frame run
-horizontally, longer ones stack on a vertical spine.
-
-```mermaid
-%% :: NOTE PIPELINE, GENERATED FROM MERMAID SOURCE
-graph LR
-md[markdown] -->|transforms| rh[rehype]
-rh -->|emits| fr[frames]
-fr -->|validates| vg[checkGrid]
-```
-
-Branching graphs delegate to mermaid-ascii. Keep edge labels to
-chains - labels on branching edges can collide (an upstream
-mermaid-ascii issue):
-
-```mermaid
-%% :: MARKDOWN FANS OUT AND REJOINS
-graph TD
-md[markdown] --> t[tables]
-md --> d[diagrams]
-t --> pg[page]
-d --> pg
-```
-
-# Code
-
-```ts
-export function pad(s: unknown, n: number): string {
-  const str = String(s ?? "");
-  return str + " ".repeat(Math.max(0, n - len(str)));
-}
-```
-
-A `diff` fence renders additions bold and deletions soft - the
-palette stays monochrome:
-
-```diff
-@@ -1,3 +1,4 @@
- export function pad(s: unknown, n: number): string {
--  return String(s).padEnd(n);
-+  const str = String(s ?? "");
-+  return str + " ".repeat(Math.max(0, n - len(str)));
- }
-```
-
-# Wikilinks
-
-A wikilink resolves by title [[Entropy Bounds in Sparse Graphs]],
-by file id [[graphs/entropy-bounds]], or doc path
-[[ooknet-org/design/frames]], and takes an alias:
-[[Entropy Bounds in Sparse Graphs|the entropy note]]. Unresolved
-targets render dead: [[A NOTE THAT DOES NOT EXIST]].
-
-# Lists
-
-- frames are strings, components are chrome
-- the grid is the contract
-  1. measure in cells
-  2. wrap at build time
-  3. never trust `ch`

diff --git a/ooknet-design/src/content/docs/
ooknet-org/style-sheet.md b/ooknet-design/sr
c/content/docs/ooknet-org/style-sheet.md
deleted file mode 100644
index 9fd4c05..0000000
--- a/ooknet-design/src/content/docs/ooknet-
org/style-sheet.md
+++ /dev/null
@@ -1,124 +0,0 @@
----
-title: "Style sheet"
-description: "Exercises every markdown cons
truct the ASCII pipeline renders."
-order: 99
----
-
-# Purpose
-
-This sheet exercises every markdown constru
ct the ASCII pipeline
-renders. If a change to the design system b
reaks layout, it should be
-visible on this page first.
-
-# Tables
-
-| GLYPH SET | CELLS | STATUS   |
-| --------- | ----: | -------- |
-| single    |    86 | ACTIVE   |
-| double    |    48 | RESERVED |
-| heavy     |    86 | DROPPED  |
-
-A wider table that must shrink and wrap on 
the narrow frame:
-
-| COMPONENT | RESPONSIBILITY               
             | NOTES                        
|
-| --------- | -----------------------------
------------ | ---------------------------- 
|
-| Frame     | renders a build function at b
oth widths   | static content only          
|
-| TextField | input seated on an underscore
 placeholder | occupies exact cells         
|
-| Select    | native dropdown inside bracke
t chrome     | arrow glyph must be on-grid  
|
-
-# Callouts
-
-> [!NOTE]
-> The frame font must ship every glyph the 
renderer emits. Unknown
-> codepoints fall back to a wider face and 
break the cell grid.
-
-> [!WARNING]
-> Interactive elements cannot dual-render: 
duplicating an input
-> duplicates its name and splits radio stat
e across variants.
-
-> A plain blockquote is unaffected by the c
allout transform and keeps
-> its italic rail styling.
-
-# Figures
-
-![Dual-width frame rendering](/figures/n0-f
rame-schematic.svg)
-
-# Diagrams
-
-A `diagram` fence renders hand-drawn ASCII 
art as a numbered figure.
-The first line may set a caption with `::`.
-
-```diagram
-:: THE SAME BUILD FUNCTION AT BOTH WIDTHS
-                ┌──────────────┐
-                │ build(width) │
-                └──────┬───────┘
-          ┌────────────┴────────────┐
-   ┌──────┴───────┐        ┌────────┴──────
─┐
-   │ FRAME_W = 86 │        │ MOBILE_W = 48 

-   │ .frame-wide  │        │ .frame-narrow 

-   └──────────────┘        └───────────────
─┘
-```
-
-A `mermaid` fence renders at build time - a
nd previews live in
-Obsidian and GitHub, since it's plain merma
id. Linear chains lay out
-through the house renderer; chains that fit
 the frame run
-horizontally, longer ones stack on a vertic
al spine.
-
-```mermaid
-%% :: NOTE PIPELINE, GENERATED FROM MERMAID
 SOURCE
-graph LR
-md[markdown] -->|transforms| rh[rehype]
-rh -->|emits| fr[frames]
-fr -->|validates| vg[checkGrid]
-```
-
-Branching graphs delegate to mermaid-ascii.
 Keep edge labels to
-chains - labels on branching edges can coll
ide (an upstream
-mermaid-ascii issue):
-
-```mermaid
-%% :: MARKDOWN FANS OUT AND REJOINS
-graph TD
-md[markdown] --> t[tables]
-md --> d[diagrams]
-t --> pg[page]
-d --> pg
-```
-
-# Code
-
-```ts
-export function pad(s: unknown, n: number):
 string {
-  const str = String(s ?? "");
-  return str + " ".repeat(Math.max(0, n - l
en(str)));
-}
-```
-
-A `diff` fence renders additions bold and d
eletions soft - the
-palette stays monochrome:
-
-```diff
-@@ -1,3 +1,4 @@
- export function pad(s: unknown, n: number)
: string {
--  return String(s).padEnd(n);
-+  const str = String(s ?? "");
-+  return str + " ".repeat(Math.max(0, n - 
len(str)));
- }
-```
-
-# Wikilinks
-
-A wikilink resolves by title [[Entropy Boun
ds in Sparse Graphs]],
-by file id [[graphs/entropy-bounds]], or do
c path
-[[ooknet-org/design/frames]], and takes an 
alias:
-[[Entropy Bounds in Sparse Graphs|the entro
py note]]. Unresolved
-targets render dead: [[A NOTE THAT DOES NOT
 EXIST]].
-
-# Lists
-
-- frames are strings, components are chrome
-- the grid is the contract
-  1. measure in cells
-  2. wrap at build time
-  3. never trust `ch`
 

diff --git a/ooknet-design/src/content/docs/ooknet/overview.md b/ooknet-design/src
/content/docs/ooknet/overview.md
deleted file mode 100644
index 080d189..0000000
--- a/ooknet-design/src/content/docs/ooknet/overview.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: "Overview"
-order: 0
----
-
-# ooknet
-
-Documentation for ooknet, my personal nixos infrastructure
-configuration. Nothing filed yet.

diff --git a/ooknet-design/src/content/docs/
ooknet/overview.md b/ooknet-design/src/conte
nt/docs/ooknet/overview.md
deleted file mode 100644
index 080d189..0000000
--- a/ooknet-design/src/content/docs/ooknet/
overview.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: "Overview"
-order: 0
----
-
-# ooknet
-
-Documentation for ooknet, my personal nixos
 infrastructure
-configuration. Nothing filed yet.
 

diff --git a/ooknet-design/src/content/docs/wowsim-stats/overview.md b/ooknet-desi
gn/src/content/docs/wowsim-stats/overview.md
deleted file mode 100644
index 9161188..0000000
--- a/ooknet-design/src/content/docs/wowsim-stats/overview.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: "Overview"
-order: 0
----
-
-# wowsim-stats
-
-Documentation for the wowsim-stats project lands here. Nothing filed
-yet.

diff --git a/ooknet-design/src/content/docs/
wowsim-stats/overview.md b/ooknet-design/src
/content/docs/wowsim-stats/overview.md
deleted file mode 100644
index 9161188..0000000
--- a/ooknet-design/src/content/docs/wowsim-
stats/overview.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: "Overview"
-order: 0
----
-
-# wowsim-stats
-
-Documentation for the wowsim-stats project 
lands here. Nothing filed
-yet.
 

diff --git a/ooknet-design/src/content/notes/ai/using-ai-to-troubleshoot.md b/ookn
et-design/src/content/notes/ai/using-ai-to-troubleshoot.md
deleted file mode 100644
index c18eb2d..0000000
--- a/ooknet-design/src/content/notes/ai/using-ai-to-troubleshoot.md
+++ /dev/null
@@ -1,181 +0,0 @@
----
-title: "USING AI TO TROUBLESHOOT AND RESOLVE ISSUES"
-published: "2026-04-27"
-status: "DRAFT"
-tags: ["ai", "troubleshooting", "operations", "case-study"]
----
-
-In this guide I want to share my experience using AI to analyse, troubleshoot and
 resolve issues.
-
-# intro
-
-For some issues the solution is clear, documented, and known. For example:
-
-> "I can't login to VPN"
-
-**Triage steps:**
-
-- Are they connected to the internet -> Connect to internet
-- Is their account locked? -> Unlock their account
-- Is their password expired -> Notify user their password has reset, walk through
 steps on how to reset password
-- Is their DUO locked -> Confirm if they where the reason for the lock -> Unlock 
DUO
-- Are they not accepting DUO prompt -> Ask them to open the DUO app and try again
-- Not receiving DUO prompt even with application open -> Verify their DUO details
 are correct (Phone number and Device)
-- Still not receiving DUO Prompt -> Restart mobile device
-- Still not recieving DUO prompt -> Re-Enroll user in DUO
-
-Clear steps to follow, no ambiguity.
-
-Another example:
-
-> "My computer is running slow"
-
-This is where it can get hazy, often the best way to proceed is to start asking m
ore questions.
-
-"When is it slow?"
-"Is it always slow?"
-"What feels slow?"
-
-Often the user doesn't have clear answers to these questions, so we start going t
hrough common troubleshooting steps:
-
-- Restart the device
-- Check start-up programs and services
-- Check if they need OS/Driver updates
-- Look through processes running in task manager
-- Check storage space
-
-Sometimes the resolution looks clear, they have too many programs/browser tabs op
en at all times and they haven't restarted their device in 24 days. You ask the us
er to change behaviour and monitor over the next week to see if the issue persists
.
-
-But what if it all looks fine? You restarted the computer, they aren't opening 10
0s of tabs in Edge, they are fully up-to-date with their updates. How do you proce
ed?
-
-A common solution is the nuclear one, replace device/re-image. This is often the 
simplest and sometimes least time consuming approach. But it's not ideal (in most 
cases). Another approach would be to dig deeper, look into the logs of the device,
 try to work out what the issue might be. This can be very time consuming, and oft
en lead to dead ends, ultimately landing you back to the nuclear option. In this d
ocument I want to provide my thoughts on how to best approach the alternative to t
he nuclear option, in an efficient manner utilising AI.
-
-# Using AI
-
-So you might decide you want to see what an LLM thinks, you prompt it with someth
ing like:
-
-> I have a user who reports their computer is running slow, any ideas?
-
-The LLM might respond with some generic troubleshooting steps we already know, or
 ask you the same questions you asked the user (to no avail).
-
-Maybe it provides some additional steps to try, clear Office cache, run a repair 
tool. It doesn't help, you go back to thinking of replacing the laptop.
-
-Maybe the issue is you didn't prompt it good enough? Maybe you need better word i
t better...
-
-> You are an expert IT system admin advising me on how best to approach a user wh
o is having trouble with a slow computer.
->  
->  User claims the computer doesn't always feel slow, just sometimes, then progra
ms start to crash and so they turn their computer off.
->
-> Steps already taken:
-> - Restarted computer
-> - Updated Drivers and OS
-> - Checked RAM and CPU usage
->   
->  All seemed fine on my end, how do I proceed?
-
-You get back an emoji filled essay of things to try, most of which are not helpfu
l to you. The problem here is not how the prompt is written, it's that we gave it 
nothing to work with. All we did was tee it up to write a "Top 10 Troubleshooting 
steps for Windows 2026" blog post.
-
-What we need to do is give it the details, provide it with the context it needs t
o help find what the real issue is.
-# Event logs
-
-Event logs are Windows' central system for logging. Every time a service starts, 
a driver loads, an app crashes, or a Windows component decides something is worth 
noting - Windows writes an entry to the event log.
-
-Typically you read it through the Event Viewer (`eventvwr.msc`) accessible via **
Start -> "Event Viewer"**. The left pane is a tree of log files. With the most com
mon "Channels" being:
-
-- **Application** - Anything an installed program logged. Crashes (Event ID 1000)
, hangs (1002), .NET errors, Office issues.
-- **System** - The operating system itself. Driver problems, service start/stop, 
disk errors, unexpected shutdowns, hardware events.
-- **Security** - logons, logoffs, privilege use, account changes.
-
-Below those are the **Applications and Services Logs**, this is where Microsoft f
eatures and third-party software keep their own private logs. This is often where 
the genuinely useful information lives. For example `Microsoft ? Windows ? Diagnos
tics-Performance ? Operational` for slow boot/shutdown investigations.
-
-Every entry has the same handful of fields:
-
-- **Level** - Critical, Error, Warning, Information, Verbose. On a healthy machin
e 99% of events are Information; filter those out first.
-- **Source** - What generated the entry (e.g. `Application Error`, `disk`, `Servi
ce Control Manager`, `Microsoft-Windows-Kernel-Power`).
-- **Event ID** - A number that uniquely identifies the _kind_ of event for that s
ource. `disk` Event ID 7 always means "the device has a bad block." Once you know 
the (Source, Event ID) pair, you can look it up.
-- **Date/Time** - When it happened. Critical for correlating with what the user r
eported.
-- **Message** - A human-readable description, usually with the useful details (fi
le path, error code, process name, exit code).
-
-Two things make event logs especially well suited to handing to an LLM:
-
-1. **They're already structured.** Each entry is self-contained. Source, ID, mess
age, timestamp - this is exactly the shape an LLM needs to reason about without yo
u having to explain the surrounding context.
-2. **Event IDs are well-documented.** Microsoft docs, vendor KBs, and twenty year
s of forum posts mean the model has almost certainly seen the (Source, ID) pair yo
u're looking at and can tell you what it usually means and what tends to cause it.
-
-Typically the challenge with Event logs comes from its volume. On an average work
ing day a Windows machine might emit thousands of events. The skill here is the ab
ility to read through the noise. In the world of InfoSec, this often involves the 
use of external tools such as Splunk and Log Parser Studio to interrogate the info
rmation you need for your investigation. For our purposes, these tools are not nee
ded, I want to propose an alternative utilising PowerShell, Excel, and Copilot.
-
-# Collecting Event logs
-
-Because all event logs are just .NET objects - exporting, manipulating, and filte
ring is easily done via PowerShell's builtin cmdlet `Get-WinEvent`.
-
-Example:
-
-``` powershell
-Get-WinEvent -FilterHashtable @{
-	LogName = 'Application'
-	Level = 1,2,3
-	StartTime = (Get-Date).AddHours(-24)
-}
-```
-
-This will return all level 1, 2 and 3 event logs from the "Application" channel. 
From there we can pipe the output to `Select-Object` to get the fields we want:
-
-``` powershell
-Get-WinEvent -FilterHashtable @{
-	LogName = 'Application'
-	Level = 1,2,3
-	StartTime = (Get-Date).AddHours(-24)
-} | Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message
-```
-
-Then pipe that to an `Export-*` of our desired format, in this case, a CSV. The `
@{...}` expression on `Message` flattens any embedded newlines into spaces, withou
t it, multi-line error messages would break the CSV across multiple rows:
-
-``` powershell
-Get-WinEvent -FilterHashtable @{
-	LogName = 'Application'
-	Level = 1,2,3
-	StartTime = (Get-Date).AddHours(-24)
-} | Select-Object TimeCreated, Id, LevelDisplayName, ProviderName,
-    @{N='Message'; E={ $_.Message -replace "`r`n",' ' }} |
-Export-Csv -NoTypeInformation -Path "$env:USERPROFILE\Desktop\events.csv"
-```
-
-This will leave us with a `csv` file of all the "Application" channel logs from t
he past 24 hours containing the fields we specified (TimeCreated, Id, LevelDisplay
Name, ProviderName, Message).
-
-I won't get too deep into the PowerShell here. You can read more about the `Get-W
inEvent` and `Export-Csv` below:
-
-- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagno
stics/get-winevent?view=powershell-7.6
-- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utilit
y/export-csv?view=powershell-7.6
-
-
-
-# Using the Event logs with AI
-
-With the exported workbook, the easiest path would be to upload it to a Copilot c
hat and ask "any ideas what's going on here?" That works, but in my experience it 
hands too much over to the LLM. You get an answer, you accept it (or you don't), a
nd the conversation is over. You've cut yourself out of the investigation, and you
've lost the chance to spot anything the model glossed over.
-
-I now mostly use the Copilot agent directly inside Excel to keep the work collabo
rative. The workbook is open in front of you, Copilot edits it as you go, and you 
can push back on what it suggests rather than just receive an answer.
-
-# Case study
-
-The following is a case study on a real world use of this technique.
-
-## The ticket
-
-**Title**: Laptop running slow, unable to complete any work
-**Description**: Laptop running slow. Unable to open and use outlook, teams or fi
les. Continuously freezing also. Unable to get any work done.
-
-## Steps taken
-
-1. Spoke to the user to get more information and learnt the following
-	- This started to happen a couple weeks ago
-	- No other useful information provided
-2. Checked disk space - Plenty of space
-3. Checked uptime - Had been rebooted recently
-4. Checked processes - Nothing stood out
-5. Check start-up software/services - Minimal
-
-When inspecting the computer, everything seemed to performing well.
-For deeper collection Microsoft's [[windows/collect-data-tss|TSS toolset]]
-is the heavier option; here I dumped some logs using the following script
-
-
-

diff --git a/ooknet-design/src/content/notes
/ai/using-ai-to-troubleshoot.md b/ooknet-des
ign/src/content/notes/ai/using-ai-to-trouble
shoot.md
deleted file mode 100644
index c18eb2d..0000000
--- a/ooknet-design/src/content/notes/ai/usi
ng-ai-to-troubleshoot.md
+++ /dev/null
@@ -1,181 +0,0 @@
----
-title: "USING AI TO TROUBLESHOOT AND RESOLV
E ISSUES"
-published: "2026-04-27"
-status: "DRAFT"
-tags: ["ai", "troubleshooting", "operations
", "case-study"]
----
-
-In this guide I want to share my experience
 using AI to analyse, troubleshoot and resol
ve issues.
-
-# intro
-
-For some issues the solution is clear, docu
mented, and known. For example:
-
-> "I can't login to VPN"
-
-**Triage steps:**
-
-- Are they connected to the internet -> Con
nect to internet
-- Is their account locked? -> Unlock their 
account
-- Is their password expired -> Notify user 
their password has reset, walk through steps
 on how to reset password
-- Is their DUO locked -> Confirm if they wh
ere the reason for the lock -> Unlock DUO
-- Are they not accepting DUO prompt -> Ask 
them to open the DUO app and try again
-- Not receiving DUO prompt even with applic
ation open -> Verify their DUO details are c
orrect (Phone number and Device)
-- Still not receiving DUO Prompt -> Restart
 mobile device
-- Still not recieving DUO prompt -> Re-Enro
ll user in DUO
-
-Clear steps to follow, no ambiguity.
-
-Another example:
-
-> "My computer is running slow"
-
-This is where it can get hazy, often the be
st way to proceed is to start asking more qu
estions.
-
-"When is it slow?"
-"Is it always slow?"
-"What feels slow?"
-
-Often the user doesn't have clear answers t
o these questions, so we start going through
 common troubleshooting steps:
-
-- Restart the device
-- Check start-up programs and services
-- Check if they need OS/Driver updates
-- Look through processes running in task ma
nager
-- Check storage space
-
-Sometimes the resolution looks clear, they 
have too many programs/browser tabs open at 
all times and they haven't restarted their d
evice in 24 days. You ask the user to change
 behaviour and monitor over the next week to
 see if the issue persists.
-
-But what if it all looks fine? You restarte
d the computer, they aren't opening 100s of 
tabs in Edge, they are fully up-to-date with
 their updates. How do you proceed?
-
-A common solution is the nuclear one, repla
ce device/re-image. This is often the simple
st and sometimes least time consuming approa
ch. But it's not ideal (in most cases). Anot
her approach would be to dig deeper, look in
to the logs of the device, try to work out w
hat the issue might be. This can be very tim
e consuming, and often lead to dead ends, ul
timately landing you back to the nuclear opt
ion. In this document I want to provide my t
houghts on how to best approach the alternat
ive to the nuclear option, in an efficient m
anner utilising AI.
-
-# Using AI
-
-So you might decide you want to see what an
 LLM thinks, you prompt it with something li
ke:
-
-> I have a user who reports their computer 
is running slow, any ideas?
-
-The LLM might respond with some generic tro
ubleshooting steps we already know, or ask y
ou the same questions you asked the user (to
 no avail).
-
-Maybe it provides some additional steps to 
try, clear Office cache, run a repair tool. 
It doesn't help, you go back to thinking of 
replacing the laptop.
-
-Maybe the issue is you didn't prompt it goo
d enough? Maybe you need better word it bett
er...
-
-> You are an expert IT system admin advisin
g me on how best to approach a user who is h
aving trouble with a slow computer.
->  
->  User claims the computer doesn't always 
feel slow, just sometimes, then programs sta
rt to crash and so they turn their computer 
off.
->
-> Steps already taken:
-> - Restarted computer
-> - Updated Drivers and OS
-> - Checked RAM and CPU usage
->   
->  All seemed fine on my end, how do I proc
eed?
-
-You get back an emoji filled essay of thing
s to try, most of which are not helpful to y
ou. The problem here is not how the prompt i
s written, it's that we gave it nothing to w
ork with. All we did was tee it up to write 
a "Top 10 Troubleshooting steps for Windows 
2026" blog post.
-
-What we need to do is give it the details, 
provide it with the context it needs to help
 find what the real issue is.
-# Event logs
-
-Event logs are Windows' central system for 
logging. Every time a service starts, a driv
er loads, an app crashes, or a Windows compo
nent decides something is worth noting - Win
dows writes an entry to the event log.
-
-Typically you read it through the Event Vie
wer (`eventvwr.msc`) accessible via **Start 
-> "Event Viewer"**. The left pane is a tree
 of log files. With the most common "Channel
s" being:
-
-- **Application** - Anything an installed p
rogram logged. Crashes (Event ID 1000), hang
s (1002), .NET errors, Office issues.
-- **System** - The operating system itself.
 Driver problems, service start/stop, disk e
rrors, unexpected shutdowns, hardware events
.
-- **Security** - logons, logoffs, privilege
 use, account changes.
-
-Below those are the **Applications and Serv
ices Logs**, this is where Microsoft feature
s and third-party software keep their own pr
ivate logs. This is often where the genuinel
y useful information lives. For example `Mic
rosoft ? Windows ? Diagnostics-Performance ?
 Operational` for slow boot/shutdown investi
gations.
-
-Every entry has the same handful of fields:
-
-- **Level** - Critical, Error, Warning, Inf
ormation, Verbose. On a healthy machine 99% 
of events are Information; filter those out 
first.
-- **Source** - What generated the entry (e.
g. `Application Error`, `disk`, `Service Con
trol Manager`, `Microsoft-Windows-Kernel-Pow
er`).
-- **Event ID** - A number that uniquely ide
ntifies the _kind_ of event for that source.
 `disk` Event ID 7 always means "the device 
has a bad block." Once you know the (Source,
 Event ID) pair, you can look it up.
-- **Date/Time** - When it happened. Critica
l for correlating with what the user reporte
d.
-- **Message** - A human-readable descriptio
n, usually with the useful details (file pat
h, error code, process name, exit code).
-
-Two things make event logs especially well 
suited to handing to an LLM:
-
-1. **They're already structured.** Each ent
ry is self-contained. Source, ID, message, t
imestamp - this is exactly the shape an LLM 
needs to reason about without you having to 
explain the surrounding context.
-2. **Event IDs are well-documented.** Micro
soft docs, vendor KBs, and twenty years of f
orum posts mean the model has almost certain
ly seen the (Source, ID) pair you're looking
 at and can tell you what it usually means a
nd what tends to cause it.
-
-Typically the challenge with Event logs com
es from its volume. On an average working da
y a Windows machine might emit thousands of 
events. The skill here is the ability to rea
d through the noise. In the world of InfoSec
, this often involves the use of external to
ols such as Splunk and Log Parser Studio to 
interrogate the information you need for you
r investigation. For our purposes, these too
ls are not needed, I want to propose an alte
rnative utilising PowerShell, Excel, and Cop
ilot.
-
-# Collecting Event logs
-
-Because all event logs are just .NET object
s - exporting, manipulating, and filtering i
s easily done via PowerShell's builtin cmdle
t `Get-WinEvent`.
-
-Example:
-
-``` powershell
-Get-WinEvent -FilterHashtable @{
-	LogName = 'Application'
-	Level = 1,2,3
-	StartTime = (Get-Date).AddHours(-24)
-}
-```
-
-This will return all level 1, 2 and 3 event
 logs from the "Application" channel. From t
here we can pipe the output to `Select-Objec
t` to get the fields we want:
-
-``` powershell
-Get-WinEvent -FilterHashtable @{
-	LogName = 'Application'
-	Level = 1,2,3
-	StartTime = (Get-Date).AddHours(-24)
-} | Select-Object TimeCreated, Id, LevelDis
playName, ProviderName, Message
-```
-
-Then pipe that to an `Export-*` of our desi
red format, in this case, a CSV. The `@{...}
` expression on `Message` flattens any embed
ded newlines into spaces, without it, multi-
line error messages would break the CSV acro
ss multiple rows:
-
-``` powershell
-Get-WinEvent -FilterHashtable @{
-	LogName = 'Application'
-	Level = 1,2,3
-	StartTime = (Get-Date).AddHours(-24)
-} | Select-Object TimeCreated, Id, LevelDis
playName, ProviderName,
-    @{N='Message'; E={ $_.Message -replace 
"`r`n",' ' }} |
-Export-Csv -NoTypeInformation -Path "$env:U
SERPROFILE\Desktop\events.csv"
-```
-
-This will leave us with a `csv` file of all
 the "Application" channel logs from the pas
t 24 hours containing the fields we specifie
d (TimeCreated, Id, LevelDisplayName, Provid
erName, Message).
-
-I won't get too deep into the PowerShell he
re. You can read more about the `Get-WinEven
t` and `Export-Csv` below:
-
-- https://learn.microsoft.com/en-us/powersh
ell/module/microsoft.powershell.diagnostics/
get-winevent?view=powershell-7.6
-- https://learn.microsoft.com/en-us/powersh
ell/module/microsoft.powershell.utility/expo
rt-csv?view=powershell-7.6
-
-
-
-# Using the Event logs with AI
-
-With the exported workbook, the easiest pat
h would be to upload it to a Copilot chat an
d ask "any ideas what's going on here?" That
 works, but in my experience it hands too mu
ch over to the LLM. You get an answer, you a
ccept it (or you don't), and the conversatio
n is over. You've cut yourself out of the in
vestigation, and you've lost the chance to s
pot anything the model glossed over.
-
-I now mostly use the Copilot agent directly
 inside Excel to keep the work collaborative
. The workbook is open in front of you, Copi
lot edits it as you go, and you can push bac
k on what it suggests rather than just recei
ve an answer.
-
-# Case study
-
-The following is a case study on a real wor
ld use of this technique.
-
-## The ticket
-
-**Title**: Laptop running slow, unable to c
omplete any work
-**Description**: Laptop running slow. Unabl
e to open and use outlook, teams or files. C
ontinuously freezing also. Unable to get any
 work done.
-
-## Steps taken
-
-1. Spoke to the user to get more informatio
n and learnt the following
-	- This started to happen a couple weeks ag
o
-	- No other useful information provided
-2. Checked disk space - Plenty of space
-3. Checked uptime - Had been rebooted recen
tly
-4. Checked processes - Nothing stood out
-5. Check start-up software/services - Minim
al
-
-When inspecting the computer, everything se
emed to performing well.
-For deeper collection Microsoft's [[windows
/collect-data-tss|TSS toolset]]
-is the heavier option; here I dumped some l
ogs using the following script
-
-
-
 

diff --git a/ooknet-design/src/content/notes/graphs/entropy-bounds.md b/ooknet-des
ign/src/content/notes/graphs/entropy-bounds.md
deleted file mode 100644
index 8d7215b..0000000
--- a/ooknet-design/src/content/notes/graphs/entropy-bounds.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "ENTROPY BOUNDS IN SPARSE GRAPHS"
-published: "2026-04-19"
-status: "PEER-CHECKED [PASS]"
-tags: ["entropy", "sparse-graphs", "spectral"]
-seeAlso:
-  - "USING AI TO TROUBLESHOOT AND RESOLVE ISSUES"
----
-
-# Intro
-
-The following note refines an earlier bound on the Shannon entropy of sparse
-d-regular graphs. Where prior work (see [44]) admitted only asymptotic
-statements, we present a finite, constructive form. The argument proceeds by
-counting closed walks of length three and four, from which the spectral gap is
-recovered without recourse to the Hoffman trick.
-
-For the convenience of the reader unfamiliar with the apparatus of ?2.1, a short
-worked example is reproduced below. The reader is invited to verify the
-inequality by hand; the calculation fits on one sheet.
-
-The curve admits a closed form in the regime d ? ?n; outside this range the
-bound becomes vacuous and one must defer to the operator-theoretic methods of
-Pembroke (forthcoming). A worked counter-example, due to a referee, appears in
-the appendix.
-
-# Explain?
-
-For the convenience of the reader unfamiliar with the apparatus of ?2.1, a short
-worked example is reproduced below. The reader is invited to verify the
-inequality by hand; the calculation fits on one sheet.
-
-```nix
-# This is a test code
-
-{
-    someAttr = {
-        value1 = 1;
-        value2 = "string";
-    };
-}
-```
-
-The curve admits a closed form in the regime d ? ?n; outside this range the
-bound becomes vacuous and one must defer to the operator-theoretic methods of
-Pembroke (forthcoming). A worked counter-example, due to a referee, appears in
-the appendix.
-
-## The truth
-
-The following note refines an earlier bound on the Shannon entropy of sparse
-d-regular graphs. Where prior work (see [44]) admitted only asymptotic
-statements, we present a finite, constructive form. The argument proceeds by
-counting closed walks of length three and four, from which the spectral gap is
-recovered without recourse to the Hoffman trick.
-
-For the convenience of the reader unfamiliar with the apparatus of ?2.1, a short
-worked example is reproduced below. The reader is invited to verify the
-inequality by hand; the calculation fits on one sheet.

diff --git a/ooknet-design/src/content/notes
/graphs/entropy-bounds.md b/ooknet-design/sr
c/content/notes/graphs/entropy-bounds.md
deleted file mode 100644
index 8d7215b..0000000
--- a/ooknet-design/src/content/notes/graphs
/entropy-bounds.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "ENTROPY BOUNDS IN SPARSE GRAPHS"
-published: "2026-04-19"
-status: "PEER-CHECKED [PASS]"
-tags: ["entropy", "sparse-graphs", "spectra
l"]
-seeAlso:
-  - "USING AI TO TROUBLESHOOT AND RESOLVE I
SSUES"
----
-
-# Intro
-
-The following note refines an earlier bound
 on the Shannon entropy of sparse
-d-regular graphs. Where prior work (see [44
]) admitted only asymptotic
-statements, we present a finite, constructi
ve form. The argument proceeds by
-counting closed walks of length three and f
our, from which the spectral gap is
-recovered without recourse to the Hoffman t
rick.
-
-For the convenience of the reader unfamilia
r with the apparatus of ?2.1, a short
-worked example is reproduced below. The rea
der is invited to verify the
-inequality by hand; the calculation fits on
 one sheet.
-
-The curve admits a closed form in the regim
e d ? ?n; outside this range the
-bound becomes vacuous and one must defer to
 the operator-theoretic methods of
-Pembroke (forthcoming). A worked counter-ex
ample, due to a referee, appears in
-the appendix.
-
-# Explain?
-
-For the convenience of the reader unfamilia
r with the apparatus of ?2.1, a short
-worked example is reproduced below. The rea
der is invited to verify the
-inequality by hand; the calculation fits on
 one sheet.
-
-```nix
-# This is a test code
-
-{
-    someAttr = {
-        value1 = 1;
-        value2 = "string";
-    };
-}
-```
-
-The curve admits a closed form in the regim
e d ? ?n; outside this range the
-bound becomes vacuous and one must defer to
 the operator-theoretic methods of
-Pembroke (forthcoming). A worked counter-ex
ample, due to a referee, appears in
-the appendix.
-
-## The truth
-
-The following note refines an earlier bound
 on the Shannon entropy of sparse
-d-regular graphs. Where prior work (see [44
]) admitted only asymptotic
-statements, we present a finite, constructi
ve form. The argument proceeds by
-counting closed walks of length three and f
our, from which the spectral gap is
-recovered without recourse to the Hoffman t
rick.
-
-For the convenience of the reader unfamilia
r with the apparatus of ?2.1, a short
-worked example is reproduced below. The rea
der is invited to verify the
-inequality by hand; the calculation fits on
 one sheet.
 

diff --git a/ooknet-design/src/content/notes/nix/nixos-thoughts.md b/ooknet-design
/src/content/notes/nix/nixos-thoughts.md
deleted file mode 100644
index 7a00936..0000000
--- a/ooknet-design/src/content/notes/nix/nixos-thoughts.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: "NIXOS THOUGHTS"
-published: "2026-07-16"
-status: "DRAFT"
-tags: ["nixos", "nix", "guide"]
-draft: true
----
-
-# Intro
-
-NixOS is a mountain, but it's one clouded in fog. It's not until you're halfway u
p, wrapped in endless layers of abstraction, that you realise how far you are from
 the top. Then something breaks. You look at the error log. Gibberish.
-
-It's at this point you realise you haven't been climbing the mountain at all. You
 look down. A sherpa has been carrying you the whole way.
-
-I want to tell you how to climb it yourself. Slow, but steady.
-
-There's a pervasive perception in the NixOS community that the documentation is p
oor. The complaint is misaimed.
-
-The comparison people reach for is the ArchWiki. But Arch is just an OS. NixOS is
n't. It's a programming language, a library for building operating systems written
 in that language, a package manager, and an OS assembled out of all of it. You ca
n't document something like that with a wiki, because most of what people want doc
umented isn't an OS.
-
-The pieces that exist are reasonable. The Nix manual covers the language, the Nix
pkgs manual covers the conventions, the NixOS manual covers the module system. Wha
t people usually mean when they say "the docs are bad" is that they couldn't find 
a specific answer to a specific composition someone wrote inside Nixpkgs. That's t
he same thing as asking for every PyPI package to be documented in the Python manu
al. No language ecosystem works like that.
-
-What I think is actually happening: people jump in at the top of the abstraction 
stack and never touch the roots. When something breaks they reach for higher-level
 docs that only make sense if you already know what's underneath. It's like thinki
ng you understand JavaScript because you installed a WordPress plugin. You haven't
 interacted with the language at all, you just imported a library.
-
-This guide builds the foundation. So when problems arise, and they will, you're p
repared to find solutions yourself.
-
-> [!NOTE]
-> This guide assumes a basic understanding of programming and command-line fundam
entals. If you don't have that and you still want to dive in:
->
-> - You'll be able to use NixOS just fine. Follow a basic install tutorial and wr
ite a configuration.
-> - If you're persistent, you'll get into some of the more "advanced" features.
-> - A lack of documentation is almost never the reason you'll struggle to find so
lutions, despite what people say.
-> - At some point you WILL have to learn how the language works, and solve proble
ms no one else has had.
-
-Before we touch NixOS itself, we're going to walk through the layers it sits on:
-
-- The language basics
-- Derivations
-- The package manager and build system
-- Nixpkgs
-- Hydra
-
-Only once that's in place do we get to NixOS.
-# The language
-
-## The Basics
-
-Go to https://nixos.org/download/ and download the package manage for you current
 OS.
-
-Verify the language is usable in your PATH
-```sh
-$ nix --version
-nix (Lix, like Nix) 2.94.1
-```
-Great, now hop into the repl
-
-```sh
-nix repl
-
-Type :? for help.
-nix-repl>
-```
-
-The Nix language is actually very simple with very little in the way of "syntax"
-
-
-

diff --git a/ooknet-design/src/content/notes
/nix/nixos-thoughts.md b/ooknet-design/src/c
ontent/notes/nix/nixos-thoughts.md
deleted file mode 100644
index 7a00936..0000000
--- a/ooknet-design/src/content/notes/nix/ni
xos-thoughts.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: "NIXOS THOUGHTS"
-published: "2026-07-16"
-status: "DRAFT"
-tags: ["nixos", "nix", "guide"]
-draft: true
----
-
-# Intro
-
-NixOS is a mountain, but it's one clouded i
n fog. It's not until you're halfway up, wra
pped in endless layers of abstraction, that 
you realise how far you are from the top. Th
en something breaks. You look at the error l
og. Gibberish.
-
-It's at this point you realise you haven't 
been climbing the mountain at all. You look 
down. A sherpa has been carrying you the who
le way.
-
-I want to tell you how to climb it yourself
. Slow, but steady.
-
-There's a pervasive perception in the NixOS
 community that the documentation is poor. T
he complaint is misaimed.
-
-The comparison people reach for is the Arch
Wiki. But Arch is just an OS. NixOS isn't. I
t's a programming language, a library for bu
ilding operating systems written in that lan
guage, a package manager, and an OS assemble
d out of all of it. You can't document somet
hing like that with a wiki, because most of 
what people want documented isn't an OS.
-
-The pieces that exist are reasonable. The N
ix manual covers the language, the Nixpkgs m
anual covers the conventions, the NixOS manu
al covers the module system. What people usu
ally mean when they say "the docs are bad" i
s that they couldn't find a specific answer 
to a specific composition someone wrote insi
de Nixpkgs. That's the same thing as asking 
for every PyPI package to be documented in t
he Python manual. No language ecosystem work
s like that.
-
-What I think is actually happening: people 
jump in at the top of the abstraction stack 
and never touch the roots. When something br
eaks they reach for higher-level docs that o
nly make sense if you already know what's un
derneath. It's like thinking you understand 
JavaScript because you installed a WordPress
 plugin. You haven't interacted with the lan
guage at all, you just imported a library.
-
-This guide builds the foundation. So when p
roblems arise, and they will, you're prepare
d to find solutions yourself.
-
-> [!NOTE]
-> This guide assumes a basic understanding 
of programming and command-line fundamentals
. If you don't have that and you still want 
to dive in:
->
-> - You'll be able to use NixOS just fine. 
Follow a basic install tutorial and write a 
configuration.
-> - If you're persistent, you'll get into s
ome of the more "advanced" features.
-> - A lack of documentation is almost never
 the reason you'll struggle to find solution
s, despite what people say.
-> - At some point you WILL have to learn ho
w the language works, and solve problems no 
one else has had.
-
-Before we touch NixOS itself, we're going t
o walk through the layers it sits on:
-
-- The language basics
-- Derivations
-- The package manager and build system
-- Nixpkgs
-- Hydra
-
-Only once that's in place do we get to NixO
S.
-# The language
-
-## The Basics
-
-Go to https://nixos.org/download/ and downl
oad the package manage for you current OS.
-
-Verify the language is usable in your PATH
-```sh
-$ nix --version
-nix (Lix, like Nix) 2.94.1
-```
-Great, now hop into the repl
-
-```sh
-nix repl
-
-Type :? for help.
-nix-repl>
-```
-
-The Nix language is actually very simple wi
th very little in the way of "syntax"
-
-
-
 

diff --git a/ooknet-design/src/git.config.ts b/ooknet-design/src/git.config.ts
index e5811a7..c58a013 100644
--- a/ooknet-design/src/git.config.ts
+++ b/ooknet-design/src/git.config.ts
@@ -16,4 +16,18 @@ export const REPOS: GitRepo[] = [
     desc: "personal website - a text-mode knowledge base and the ascii design sys
tem that renders it",
     clone: "https://git.ooknet.org/ooks/ooknet.org.git",
   },
+  {
+    slug: "ooknet",
+    name: "ooknet",
+    path: at("ooknet", "../../ooknet"),
+    desc: "my personal nixos infrastructure configuration",
+    clone: "https://git.ooknet.org/ooks/ooknet.git",
+  },
+  {
+    slug: "wowsim-stats",
+    name: "wowsim-stats",
+    path: at("wowsim-stats", "../../wowsim-stats"),
+    desc: "a nix pipeline that orchestrates mist of pandaria simulations at scale
, and a statically generated challenge mode leaderboard",
+    clone: "https://git.ooknet.org/ooks/wowsim-stats.git",
+  },
 ];

diff --git a/ooknet-design/src/git.config.ts
 b/ooknet-design/src/git.config.ts
index e5811a7..c58a013 100644
--- a/ooknet-design/src/git.config.ts
+++ b/ooknet-design/src/git.config.ts
@@ -16,4 +16,18 @@ export const REPOS: GitRe
po[] = [
     desc: "personal website - a text-mode k
nowledge base and the ascii design system th
at renders it",
     clone: "https://git.ooknet.org/ooks/ook
net.org.git",
   },
+  {
+    slug: "ooknet",
+    name: "ooknet",
+    path: at("ooknet", "../../ooknet"),
+    desc: "my personal nixos infrastructure
 configuration",
+    clone: "https://git.ooknet.org/ooks/ook
net.git",
+  },
+  {
+    slug: "wowsim-stats",
+    name: "wowsim-stats",
+    path: at("wowsim-stats", "../../wowsim-
stats"),
+    desc: "a nix pipeline that orchestrates
 mist of pandaria simulations at scale, and 
a statically generated challenge mode leader
board",
+    clone: "https://git.ooknet.org/ooks/wow
sim-stats.git",
+  },
 ];
 

diff --git a/ooknet-design/src/lib/wikilinks.test.ts b/ooknet-design/src/lib/wikil
inks.test.ts
index a8b7d90..34a53c1 100644
--- a/ooknet-design/src/lib/wikilinks.test.ts
+++ b/ooknet-design/src/lib/wikilinks.test.ts
@@ -1,46 +1,67 @@
-import { describe, expect, it } from "vitest";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { join } from "node:path";
+import { tmpdir } from "node:os";
+import { afterAll, beforeAll, describe, expect, it } from "vitest";
 import { buildBacklinks, buildWikiMap, extractWikiTargets, resolveWikilink } from
 "./wikilinks";
 import { wikiNodes } from "./remark-wikilinks";
 
-describe("buildWikiMap", () => {
-  const map = buildWikiMap();
+// fixture content dir - the graph tests must not depend on what the
+// real collections currently hold
+let dir: string;
+const write = (rel: string, fm: string, body = "") => {
+  const p = join(dir, rel);
+  mkdirSync(join(p, ".."), { recursive: true });
+  writeFileSync(p, `---\n${fm}\n---\n\n${body}`);
+};
+
+beforeAll(() => {
+  dir = mkdtempSync(join(tmpdir(), "wiki-"));
+  write("notes/nix/flakes.md", 'title: "FLAKES FIELD NOTES"', "cites [[windows/ts
s]] and [[Docs Page]].");
+  write("notes/nix/draft.md", 'title: "DRAFT"\ndraft: true', "cites [[flakes fiel
d notes]].");
+  write("kb/windows/tss.md", 'title: "TSS TOOLSET"\nsource: "MS"', "literal `[[fl
akes field notes]]` only.");
+  write("docs/site/page.md", 'title: "Docs Page"', "self [[Docs Page]], live [[FL
AKES FIELD NOTES]], dead [[nope]].");
+});
+afterAll(() => rmSync(dir, { recursive: true, force: true }));
 
+describe("buildWikiMap", () => {
   it("indexes every collection by id and title", () => {
-    expect(resolveWikilink("graphs/entropy-bounds", map)).toBe("/notes/graphs/ent
ropy-bounds/");
-    expect(resolveWikilink("Entropy Bounds in Sparse Graphs", map)).toBe("/notes/
graphs/entropy-bounds/");
-    expect(resolveWikilink("windows/collect-data-tss", map)).toBe("/kb/windows/co
llect-data-tss/");
-    expect(resolveWikilink("Style Sheet", map)).toBe("/docs/ooknet-org/style-shee
t/");
+    const map = buildWikiMap(dir);
+    expect(resolveWikilink("nix/flakes", map)).toBe("/notes/nix/flakes/");
+    expect(resolveWikilink("Flakes Field Notes", map)).toBe("/notes/nix/flakes/")
;
+    expect(resolveWikilink("windows/tss", map)).toBe("/kb/windows/tss/");
+    expect(resolveWikilink("Docs Page", map)).toBe("/docs/site/page/");
   });
 
   it("is case-insensitive and trims", () => {
-    expect(resolveWikilink("  WIKILINKS  ", map)).toBe("/docs/ooknet-org/authorin
g/wikilinks/");
+    const map = buildWikiMap(dir);
+    expect(resolveWikilink("  TSS TOOLSET  ", map)).toBe("/kb/windows/tss/");
   });
 
   it("excludes drafts", () => {
-    expect(resolveWikilink("nix/nixos-thoughts", map)).toBeNull();
+    expect(resolveWikilink("nix/draft", buildWikiMap(dir))).toBeNull();
   });
 
   it("returns null for unknown targets", () => {
-    expect(resolveWikilink("A NOTE THAT DOES NOT EXIST", map)).toBeNull();
+    expect(resolveWikilink("A NOTE THAT DOES NOT EXIST", buildWikiMap(dir))).toBe
Null();
   });
 });
 
 describe("wikiNodes", () => {
-  const map = new Map([["style sheet", "/docs/ooknet-org/style-sheet/"]]);
+  const map = new Map([["docs page", "/docs/site/page/"]]);
 
   it("leaves plain text untouched", () => {
     expect(wikiNodes("no links here", map)).toBeNull();
   });
 
   it("splits text around a resolved link", () => {
-    const nodes = wikiNodes("see [[Style Sheet]] for glyphs", map)!;
+    const nodes = wikiNodes("see [[Docs Page]] for glyphs", map)!;
     expect(nodes.map((n) => n.type)).toEqual(["text", "link", "text"]);
-    expect(nodes[1].url).toBe("/docs/ooknet-org/style-sheet/");
+    expect(nodes[1].url).toBe("/docs/site/page/");
   });
 
   it("uses the alias as the label", () => {
-    const nodes = wikiNodes("[[Style Sheet|the sheet]]", map)!;
-    expect((nodes[0].children as { value: string }[])[0].value).toBe("the sheet")
;
+    const nodes = wikiNodes("[[Docs Page|the page]]", map)!;
+    expect((nodes[0].children as { value: string }[])[0].value).toBe("the page");
   });
 
   it("renders unresolved targets as dead-link spans", () => {
@@ -62,33 +83,32 @@ describe("extractWikiTargets", () => {
 });
 
 describe("buildBacklinks", () => {
-  const bl = buildBacklinks();
-  const urls = (target: string) => (bl.get(target) ?? []).map((r) => r.url);
-
   it("inverts the graph across collections", () => {
-    // the ai note cites the kb entry
-    expect(urls("/kb/windows/collect-data-tss/")).toContain("/notes/ai/using-ai-t
o-troubleshoot/");
-    // the style-sheet doc cites the entropy note
-    expect(urls("/notes/graphs/entropy-bounds/")).toContain("/docs/ooknet-org/sty
le-sheet/");
+    const bl = buildBacklinks(dir);
+    const urls = (t: string) => (bl.get(t) ?? []).map((r) => r.url);
+    expect(urls("/kb/windows/tss/")).toEqual(["/notes/nix/flakes/"]);
+    expect(urls("/notes/nix/flakes/")).toEqual(["/docs/site/page/"]);
   });
 
   it("labels sources with their collection", () => {
-    const src = bl
-      .get("/kb/windows/collect-data-tss/")!
-      .find((r) => r.url === "/notes/ai/using-ai-to-troubleshoot/")!;
-    expect(src.label).toBe("NOTES - USING AI TO TROUBLESHOOT AND RESOLVE ISSUES")
;
+    const src = buildBacklinks(dir).get("/kb/windows/tss/")![0];
+    expect(src.label).toBe("NOTES - FLAKES FIELD NOTES");
   });
 
-  it("drops self-references and drafts", () => {
+  it("drops self-references, drafts, and code-span citations", () => {
+    const bl = buildBacklinks(dir);
     for (const [target, list] of bl) {
       expect(list.map((r) => r.url)).not.toContain(target);
-      expect(list.map((r) => r.url)).not.toContain("/notes/nix/nixos-thoughts/");
+      expect(list.map((r) => r.url)).not.toContain("/notes/nix/draft/");
     }
+    // the kb body only cites in a code span - it must not be a source
+    const flakes = bl.get("/notes/nix/flakes/") ?? [];
+    expect(flakes.map((r) => r.url)).not.toContain("/kb/windows/tss/");
   });
 
   it("sorts sources notes, kb, docs", () => {
     const order = ["/notes/", "/kb/", "/docs/"];
-    for (const list of bl.values()) {
+    for (const list of buildBacklinks(dir).values()) {
       const kinds = list.map((r) => order.findIndex((p) => r.url.startsWith(p)));
       expect(kinds).toEqual([...kinds].sort());
     }

diff --git a/ooknet-design/src/lib/wikilinks
.test.ts b/ooknet-design/src/lib/wikilinks.t
est.ts
index a8b7d90..34a53c1 100644
--- a/ooknet-design/src/lib/wikilinks.test.t
s
+++ b/ooknet-design/src/lib/wikilinks.test.t
s
@@ -1,46 +1,67 @@
-import { describe, expect, it } from "vites
t";
+import { mkdtempSync, mkdirSync, rmSync, wr
iteFileSync } from "node:fs";
+import { join } from "node:path";
+import { tmpdir } from "node:os";
+import { afterAll, beforeAll, describe, exp
ect, it } from "vitest";
 import { buildBacklinks, buildWikiMap, extr
actWikiTargets, resolveWikilink } from "./wi
kilinks";
 import { wikiNodes } from "./remark-wikilin
ks";
 
-describe("buildWikiMap", () => {
-  const map = buildWikiMap();
+// fixture content dir - the graph tests mu
st not depend on what the
+// real collections currently hold
+let dir: string;
+const write = (rel: string, fm: string, bod
y = "") => {
+  const p = join(dir, rel);
+  mkdirSync(join(p, ".."), { recursive: tru
e });
+  writeFileSync(p, `---\n${fm}\n---\n\n${bo
dy}`);
+};
+
+beforeAll(() => {
+  dir = mkdtempSync(join(tmpdir(), "wiki-")
);
+  write("notes/nix/flakes.md", 'title: "FLA
KES FIELD NOTES"', "cites [[windows/tss]] an
d [[Docs Page]].");
+  write("notes/nix/draft.md", 'title: "DRAF
T"\ndraft: true', "cites [[flakes field note
s]].");
+  write("kb/windows/tss.md", 'title: "TSS T
OOLSET"\nsource: "MS"', "literal `[[flakes f
ield notes]]` only.");
+  write("docs/site/page.md", 'title: "Docs 
Page"', "self [[Docs Page]], live [[FLAKES F
IELD NOTES]], dead [[nope]].");
+});
+afterAll(() => rmSync(dir, { recursive: tru
e, force: true }));
 
+describe("buildWikiMap", () => {
   it("indexes every collection by id and ti
tle", () => {
-    expect(resolveWikilink("graphs/entropy-
bounds", map)).toBe("/notes/graphs/entropy-b
ounds/");
-    expect(resolveWikilink("Entropy Bounds 
in Sparse Graphs", map)).toBe("/notes/graphs
/entropy-bounds/");
-    expect(resolveWikilink("windows/collect
-data-tss", map)).toBe("/kb/windows/collect-
data-tss/");
-    expect(resolveWikilink("Style Sheet", m
ap)).toBe("/docs/ooknet-org/style-sheet/");
+    const map = buildWikiMap(dir);
+    expect(resolveWikilink("nix/flakes", ma
p)).toBe("/notes/nix/flakes/");
+    expect(resolveWikilink("Flakes Field No
tes", map)).toBe("/notes/nix/flakes/");
+    expect(resolveWikilink("windows/tss", m
ap)).toBe("/kb/windows/tss/");
+    expect(resolveWikilink("Docs Page", map
)).toBe("/docs/site/page/");
   });
 
   it("is case-insensitive and trims", () =>
 {
-    expect(resolveWikilink("  WIKILINKS  ",
 map)).toBe("/docs/ooknet-org/authoring/wiki
links/");
+    const map = buildWikiMap(dir);
+    expect(resolveWikilink("  TSS TOOLSET  
", map)).toBe("/kb/windows/tss/");
   });
 
   it("excludes drafts", () => {
-    expect(resolveWikilink("nix/nixos-thoug
hts", map)).toBeNull();
+    expect(resolveWikilink("nix/draft", bui
ldWikiMap(dir))).toBeNull();
   });
 
   it("returns null for unknown targets", ()
 => {
-    expect(resolveWikilink("A NOTE THAT DOE
S NOT EXIST", map)).toBeNull();
+    expect(resolveWikilink("A NOTE THAT DOE
S NOT EXIST", buildWikiMap(dir))).toBeNull()
;
   });
 });
 
 describe("wikiNodes", () => {
-  const map = new Map([["style sheet", "/do
cs/ooknet-org/style-sheet/"]]);
+  const map = new Map([["docs page", "/docs
/site/page/"]]);
 
   it("leaves plain text untouched", () => {
     expect(wikiNodes("no links here", map))
.toBeNull();
   });
 
   it("splits text around a resolved link", 
() => {
-    const nodes = wikiNodes("see [[Style Sh
eet]] for glyphs", map)!;
+    const nodes = wikiNodes("see [[Docs Pag
e]] for glyphs", map)!;
     expect(nodes.map((n) => n.type)).toEqua
l(["text", "link", "text"]);
-    expect(nodes[1].url).toBe("/docs/ooknet
-org/style-sheet/");
+    expect(nodes[1].url).toBe("/docs/site/p
age/");
   });
 
   it("uses the alias as the label", () => {
-    const nodes = wikiNodes("[[Style Sheet|
the sheet]]", map)!;
-    expect((nodes[0].children as { value: s
tring }[])[0].value).toBe("the sheet");
+    const nodes = wikiNodes("[[Docs Page|th
e page]]", map)!;
+    expect((nodes[0].children as { value: s
tring }[])[0].value).toBe("the page");
   });
 
   it("renders unresolved targets as dead-li
nk spans", () => {
@@ -62,33 +83,32 @@ describe("extractWikiTar
gets", () => {
 });
 
 describe("buildBacklinks", () => {
-  const bl = buildBacklinks();
-  const urls = (target: string) => (bl.get(
target) ?? []).map((r) => r.url);
-
   it("inverts the graph across collections"
, () => {
-    // the ai note cites the kb entry
-    expect(urls("/kb/windows/collect-data-t
ss/")).toContain("/notes/ai/using-ai-to-trou
bleshoot/");
-    // the style-sheet doc cites the entrop
y note
-    expect(urls("/notes/graphs/entropy-boun
ds/")).toContain("/docs/ooknet-org/style-she
et/");
+    const bl = buildBacklinks(dir);
+    const urls = (t: string) => (bl.get(t) 
?? []).map((r) => r.url);
+    expect(urls("/kb/windows/tss/")).toEqua
l(["/notes/nix/flakes/"]);
+    expect(urls("/notes/nix/flakes/")).toEq
ual(["/docs/site/page/"]);
   });
 
   it("labels sources with their collection"
, () => {
-    const src = bl
-      .get("/kb/windows/collect-data-tss/")
!
-      .find((r) => r.url === "/notes/ai/usi
ng-ai-to-troubleshoot/")!;
-    expect(src.label).toBe("NOTES - USING A
I TO TROUBLESHOOT AND RESOLVE ISSUES");
+    const src = buildBacklinks(dir).get("/k
b/windows/tss/")![0];
+    expect(src.label).toBe("NOTES - FLAKES 
FIELD NOTES");
   });
 
-  it("drops self-references and drafts", ()
 => {
+  it("drops self-references, drafts, and co
de-span citations", () => {
+    const bl = buildBacklinks(dir);
     for (const [target, list] of bl) {
       expect(list.map((r) => r.url)).not.to
Contain(target);
-      expect(list.map((r) => r.url)).not.to
Contain("/notes/nix/nixos-thoughts/");
+      expect(list.map((r) => r.url)).not.to
Contain("/notes/nix/draft/");
     }
+    // the kb body only cites in a code spa
n - it must not be a source
+    const flakes = bl.get("/notes/nix/flake
s/") ?? [];
+    expect(flakes.map((r) => r.url)).not.to
Contain("/kb/windows/tss/");
   });
 
   it("sorts sources notes, kb, docs", () =>
 {
     const order = ["/notes/", "/kb/", "/doc
s/"];
-    for (const list of bl.values()) {
+    for (const list of buildBacklinks(dir).
values()) {
       const kinds = list.map((r) => order.f
indIndex((p) => r.url.startsWith(p)));
       expect(kinds).toEqual([...kinds].sort
());
     }
 

diff --git a/ooknet-design/src/pages/about.astro b/ooknet-design/src/pages/about.a
stro
index 0c0eaca..23a1374 100644
--- a/ooknet-design/src/pages/about.astro
+++ b/ooknet-design/src/pages/about.astro
@@ -3,7 +3,6 @@ import ArticleLayout from "../layouts/ArticleLayout/ArticleLayout.
astro";
 import TopHeader from "../components/TopHeader/TopHeader.astro";
 import Frame from "../components/Frame/Frame.astro";
 import Pre from "../components/Pre/Pre.astro";
-import Prose from "../components/Prose/Prose.astro";
 import ArticleFooter from "../components/ArticleFooter/ArticleFooter.astro";
 import { kv, row2, rule, ruleD } from "../lib/ascii";
 
@@ -24,27 +23,11 @@ const buildColophon = (w: number) =>
   ].join("\n");
 ---
 <ArticleLayout title="OOKNET KB - About"
-  description="What OOKNET is: a personal knowledge base rendered as pure text on
 a monospace grid - every border, chart, and frame is a real character.">
+  description="OOKNET - a personal knowledge base rendered as pure text on a mono
space grid.">
   <TopHeader />
   <Frame build={buildHeader} />
-  <Prose>
-    <p>
-      OOKNET is a personal knowledge base kept the way records used to
-      be kept: as numbered technical sheets, filed under a taxonomy,
-      printed on an endless roll of tractor-feed paper. Notes get a
-      number, a status, and a place in the filing system; the archive
-      grows from the bottom of the index.
-    </p>
-    <p>
-      The site is also its own subject. Every frame on these pages is a
-      string composed at build time on a strict monospace grid - no
-      client-side rendering, no images of boxes, just characters. A
-      validator walks every line at compile time and refuses to ship a
-      bent border. The whole apparatus is documented on the style sheet
-      at note N0 and demonstrated on the components page.
-    </p>
-  </Prose>
+  <Pre>{" "}</Pre>
   <Frame build={buildColophon} />
   <Pre>{" "}</Pre>
-  <ArticleFooter page={4} />
+  <ArticleFooter />
 </ArticleLayout>

diff --git a/ooknet-design/src/pages/about.a
stro b/ooknet-design/src/pages/about.astro
index 0c0eaca..23a1374 100644
--- a/ooknet-design/src/pages/about.astro
+++ b/ooknet-design/src/pages/about.astro
@@ -3,7 +3,6 @@ import ArticleLayout from ".
./layouts/ArticleLayout/ArticleLayout.astro"
;
 import TopHeader from "../components/TopHea
der/TopHeader.astro";
 import Frame from "../components/Frame/Fram
e.astro";
 import Pre from "../components/Pre/Pre.astr
o";
-import Prose from "../components/Prose/Pros
e.astro";
 import ArticleFooter from "../components/Ar
ticleFooter/ArticleFooter.astro";
 import { kv, row2, rule, ruleD } from "../l
ib/ascii";
 
@@ -24,27 +23,11 @@ const buildColophon = (w
: number) =>
   ].join("\n");
 ---
 <ArticleLayout title="OOKNET KB - About"
-  description="What OOKNET is: a personal k
nowledge base rendered as pure text on a mon
ospace grid - every border, chart, and frame
 is a real character.">
+  description="OOKNET - a personal knowledg
e base rendered as pure text on a monospace 
grid.">
   <TopHeader />
   <Frame build={buildHeader} />
-  <Prose>
-    <p>
-      OOKNET is a personal knowledge base k
ept the way records used to
-      be kept: as numbered technical sheets
, filed under a taxonomy,
-      printed on an endless roll of tractor
-feed paper. Notes get a
-      number, a status, and a place in the 
filing system; the archive
-      grows from the bottom of the index.
-    </p>
-    <p>
-      The site is also its own subject. Eve
ry frame on these pages is a
-      string composed at build time on a st
rict monospace grid - no
-      client-side rendering, no images of b
oxes, just characters. A
-      validator walks every line at compile
 time and refuses to ship a
-      bent border. The whole apparatus is d
ocumented on the style sheet
-      at note N0 and demonstrated on the co
mponents page.
-    </p>
-  </Prose>
+  <Pre>{" "}</Pre>
   <Frame build={buildColophon} />
   <Pre>{" "}</Pre>
-  <ArticleFooter page={4} />
+  <ArticleFooter />
 </ArticleLayout>
 

diff --git a/ooknet-design/src/pages/status.astro b/ooknet-design/src/pages/status
.astro
index 8fbc2b5..78e5f1c 100644
--- a/ooknet-design/src/pages/status.astro
+++ b/ooknet-design/src/pages/status.astro
@@ -1,6 +1,7 @@
 ---
 // System telemetry - dogfoods the display components with real numbers
-// computed from the collections at build time.
+// computed from the collections at build time. Every section guards
+// against an empty collection; the archive starts somewhere.
 import { getCollection } from "astro:content";
 import ArticleLayout from "../layouts/ArticleLayout/ArticleLayout.astro";
 import TopHeader from "../components/TopHeader/TopHeader.astro";
@@ -30,9 +31,9 @@ const words = notes.map((n) => (n.body ?? "").split(/\s+/).filte
r(Boolean).lengt
 const totalWords = words.reduce((a, b) => a + b, 0);
 
 const byDate = [...notes].sort((a, b) => (a.data.published < b.data.published ? 1
 : -1));
-const latestDate = byDate[0].data.published;
-const [year, month] = latestDate.split("-").map(Number);
-const monthPrefix = `${year}-${String(month).padStart(2, "0")}`;
+const latestDate = byDate[0]?.data.published ?? null;
+const [year, month] = (latestDate ?? "").split("-").map(Number);
+const monthPrefix = latestDate ? `${year}-${String(month).padStart(2, "0")}` : ""
;
 const marks = byDate
   .filter((n) => n.data.published.startsWith(monthPrefix))
   .map((n) => Number(n.data.published.slice(8)));
@@ -56,7 +57,7 @@ const log: TimelineEntry[] = byDate.map((n) => ({
 <ArticleLayout title="OOKNET KB - Status"
   description="OOKNET system telemetry - pages on file, tag distribution, and fil
ing activity, charted in text.">
   <TopHeader />
-  <Frame build={(w) => [row2("SYSTEM TELEMETRY", `AS OF ${latestDate}`, w), ruleD
(w)].join("\n")} />
+  <Frame build={(w) => [row2("SYSTEM TELEMETRY", latestDate ? `AS OF ${latestDate
}` : "AWAITING FILINGS", w), ruleD(w)].join("\n")} />
   <Pre>{" "}</Pre>
 
   <BigNumber value={total} label="PAGES ON FILE" />
@@ -66,30 +67,48 @@ const log: TimelineEntry[] = byDate.map((n) => ({
     ["NOTES", `${notes.length} FILED`],
     ["KB", `${kb.length} ON FILE`],
     ["DOCS", `${docs.length} PAGES`],
-    ["WORDS", `${totalWords} TOTAL / ${Math.round(totalWords / notes.length)} AVG
 PER NOTE`],
-    ["LAST FILING", latestDate],
+    ...(notes.length > 0
+      ? [["WORDS", `${totalWords} TOTAL / ${Math.round(totalWords / notes.length)
} AVG PER NOTE`] as [string, string]]
+      : []),
+    ...(latestDate ? [["LAST FILING", latestDate] as [string, string]] : []),
   ]} keyW={LABEL_W + 2} />
   <Pre>{" "}</Pre>
 
-  <Frame build={section("SIGNAL", "words per note, chronological")} />
-  <Pre>{" "}</Pre>
-  <Pre>{pad("WORDS/NOTE", LABEL_W)}<Sparkline values={words} width={40} />{`  ${M
ath.max(...words)} PEAK`}</Pre>
-  <Pre>{" "}</Pre>
+  {notes.length > 0 && (
+    <Fragment>
+      <Frame build={section("SIGNAL", "words per note, chronological")} />
+      <Pre>{" "}</Pre>
+      <Pre>{pad("WORDS/NOTE", LABEL_W)}<Sparkline values={words} width={40} />{` 
 ${Math.max(...words)} PEAK`}</Pre>
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
-  <Frame build={section("TAXONOMY", `tags across notes + kb, top ${topTags.length
}`)} />
-  <Pre>{" "}</Pre>
-  <BarChart items={topTags} />
-  <Pre>{" "}</Pre>
+  {topTags.length > 0 && (
+    <Fragment>
+      <Frame build={section("TAXONOMY", `tags across notes + kb, top ${topTags.le
ngth}`)} />
+      <Pre>{" "}</Pre>
+      <BarChart items={topTags} />
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
-  <Panel title={`FILINGS ${monthPrefix}`}>
-    <Calendar year={year} month={month} marks={marks} />
-  </Panel>
-  <Pre>{" "}</Pre>
+  {latestDate && (
+    <Fragment>
+      <Panel title={`FILINGS ${monthPrefix}`}>
+        <Calendar year={year} month={month} marks={marks} />
+      </Panel>
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
-  <Frame build={section("FILING LOG", "most recent first")} />
-  <Pre>{" "}</Pre>
-  <Timeline entries={log} />
-  <Pre>{" "}</Pre>
+  {log.length > 0 && (
+    <Fragment>
+      <Frame build={section("FILING LOG", "most recent first")} />
+      <Pre>{" "}</Pre>
+      <Timeline entries={log} />
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
   <Frame build={(w) => rule(w)} />
 </ArticleLayout>

diff --git a/ooknet-design/src/pages/status.
astro b/ooknet-design/src/pages/status.astro
index 8fbc2b5..78e5f1c 100644
--- a/ooknet-design/src/pages/status.astro
+++ b/ooknet-design/src/pages/status.astro
@@ -1,6 +1,7 @@
 ---
 // System telemetry - dogfoods the display 
components with real numbers
-// computed from the collections at build t
ime.
+// computed from the collections at build t
ime. Every section guards
+// against an empty collection; the archive
 starts somewhere.
 import { getCollection } from "astro:conten
t";
 import ArticleLayout from "../layouts/Artic
leLayout/ArticleLayout.astro";
 import TopHeader from "../components/TopHea
der/TopHeader.astro";
@@ -30,9 +31,9 @@ const words = notes.map((n
) => (n.body ?? "").split(/\s+/).filter(Bool
ean).lengt
 const totalWords = words.reduce((a, b) => a
 + b, 0);
 
 const byDate = [...notes].sort((a, b) => (a
.data.published < b.data.published ? 1 : -1)
);
-const latestDate = byDate[0].data.published
;
-const [year, month] = latestDate.split("-")
.map(Number);
-const monthPrefix = `${year}-${String(month
).padStart(2, "0")}`;
+const latestDate = byDate[0]?.data.publishe
d ?? null;
+const [year, month] = (latestDate ?? "").sp
lit("-").map(Number);
+const monthPrefix = latestDate ? `${year}-$
{String(month).padStart(2, "0")}` : "";
 const marks = byDate
   .filter((n) => n.data.published.startsWit
h(monthPrefix))
   .map((n) => Number(n.data.published.slice
(8)));
@@ -56,7 +57,7 @@ const log: TimelineEntry[]
 = byDate.map((n) => ({
 <ArticleLayout title="OOKNET KB - Status"
   description="OOKNET system telemetry - pa
ges on file, tag distribution, and filing ac
tivity, charted in text.">
   <TopHeader />
-  <Frame build={(w) => [row2("SYSTEM TELEME
TRY", `AS OF ${latestDate}`, w), ruleD(w)].j
oin("\n")} />
+  <Frame build={(w) => [row2("SYSTEM TELEME
TRY", latestDate ? `AS OF ${latestDate}` : "
AWAITING FILINGS", w), ruleD(w)].join("\n")}
 />
   <Pre>{" "}</Pre>
 
   <BigNumber value={total} label="PAGES ON 
FILE" />
@@ -66,30 +67,48 @@ const log: TimelineEntry
[] = byDate.map((n) => ({
     ["NOTES", `${notes.length} FILED`],
     ["KB", `${kb.length} ON FILE`],
     ["DOCS", `${docs.length} PAGES`],
-    ["WORDS", `${totalWords} TOTAL / ${Math
.round(totalWords / notes.length)} AVG PER N
OTE`],
-    ["LAST FILING", latestDate],
+    ...(notes.length > 0
+      ? [["WORDS", `${totalWords} TOTAL / $
{Math.round(totalWords / notes.length)} AVG 
PER NOTE`] as [string, string]]
+      : []),
+    ...(latestDate ? [["LAST FILING", lates
tDate] as [string, string]] : []),
   ]} keyW={LABEL_W + 2} />
   <Pre>{" "}</Pre>
 
-  <Frame build={section("SIGNAL", "words pe
r note, chronological")} />
-  <Pre>{" "}</Pre>
-  <Pre>{pad("WORDS/NOTE", LABEL_W)}<Sparkli
ne values={words} width={40} />{`  ${Math.ma
x(...words)} PEAK`}</Pre>
-  <Pre>{" "}</Pre>
+  {notes.length > 0 && (
+    <Fragment>
+      <Frame build={section("SIGNAL", "word
s per note, chronological")} />
+      <Pre>{" "}</Pre>
+      <Pre>{pad("WORDS/NOTE", LABEL_W)}<Spa
rkline values={words} width={40} />{`  ${Mat
h.max(...words)} PEAK`}</Pre>
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
-  <Frame build={section("TAXONOMY", `tags a
cross notes + kb, top ${topTags.length}`)} /
>
-  <Pre>{" "}</Pre>
-  <BarChart items={topTags} />
-  <Pre>{" "}</Pre>
+  {topTags.length > 0 && (
+    <Fragment>
+      <Frame build={section("TAXONOMY", `ta
gs across notes + kb, top ${topTags.length}`
)} />
+      <Pre>{" "}</Pre>
+      <BarChart items={topTags} />
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
-  <Panel title={`FILINGS ${monthPrefix}`}>
-    <Calendar year={year} month={month} mar
ks={marks} />
-  </Panel>
-  <Pre>{" "}</Pre>
+  {latestDate && (
+    <Fragment>
+      <Panel title={`FILINGS ${monthPrefix}
`}>
+        <Calendar year={year} month={month}
 marks={marks} />
+      </Panel>
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
-  <Frame build={section("FILING LOG", "most
 recent first")} />
-  <Pre>{" "}</Pre>
-  <Timeline entries={log} />
-  <Pre>{" "}</Pre>
+  {log.length > 0 && (
+    <Fragment>
+      <Frame build={section("FILING LOG", "
most recent first")} />
+      <Pre>{" "}</Pre>
+      <Timeline entries={log} />
+      <Pre>{" "}</Pre>
+    </Fragment>
+  )}
 
   <Frame build={(w) => rule(w)} />
 </ArticleLayout>
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET