OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      c9aad19bfab6
DATE      2026-07-17
SUBJECT   web: referenced-by backlinks from the wikilink graph + fix docs pagination
          hrefs
FILES     11 CHANGED
HASH      c9aad19bfab6
DATE      2026-07-17
SUBJECT   web: referenced-by backlinks from the
          wikilink graph + fix docs pagination
          hrefs
FILES     11 CHANGED
 

diff --git a/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.astro 
b/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.astro
new file mode 100644
index 0000000..6303689
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.astro
@@ -0,0 +1,27 @@
+---
+import Pre from "../Pre/Pre.astro";
+import { FRAME_W, MOBILE_FRAME_W } from "../../lib/config";
+import type { Backlink } from "../../lib/wikilinks";
+import { BACKLINKS_LABEL, BACKLINKS_INDENT, layoutBacklinks, type Props } from ".
/ArticleBacklinks";
+import "./ArticleBacklinks.scss";
+
+const { refs } = Astro.props as Props;
+const indent = " ".repeat(BACKLINKS_INDENT);
+
+const variants: [string, Backlink[]][] = [
+  ["frame-wide", layoutBacklinks(refs, FRAME_W)],
+  ["frame-narrow", layoutBacklinks(refs, MOBILE_FRAME_W)],
+];
+---
+{refs.length > 0 && (
+  <nav class="backlinks" aria-label="Referenced by">
+    {variants.map(([variant, rows]) => (
+      <Fragment>
+        <Pre class={variant}>{BACKLINKS_LABEL}</Pre>
+        {rows.map((r) => (
+          <Pre class={variant}>{indent}<a class="backlink-link" href={r.url}>{r.l
abel}</a></Pre>
+        ))}
+      </Fragment>
+    ))}
+  </nav>
+)}

diff --git a/ooknet-design/src/components/Ar
ticleBacklinks/ArticleBacklinks.astro b/ookn
et-design/src/components/ArticleBacklinks/Ar
ticleBacklinks.astro
new file mode 100644
index 0000000..6303689
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBa
cklinks/ArticleBacklinks.astro
@@ -0,0 +1,27 @@
+---
+import Pre from "../Pre/Pre.astro";
+import { FRAME_W, MOBILE_FRAME_W } from "..
/../lib/config";
+import type { Backlink } from "../../lib/wi
kilinks";
+import { BACKLINKS_LABEL, BACKLINKS_INDENT,
 layoutBacklinks, type Props } from "./Artic
leBacklinks";
+import "./ArticleBacklinks.scss";
+
+const { refs } = Astro.props as Props;
+const indent = " ".repeat(BACKLINKS_INDENT)
;
+
+const variants: [string, Backlink[]][] = [
+  ["frame-wide", layoutBacklinks(refs, FRAM
E_W)],
+  ["frame-narrow", layoutBacklinks(refs, MO
BILE_FRAME_W)],
+];
+---
+{refs.length > 0 && (
+  <nav class="backlinks" aria-label="Refere
nced by">
+    {variants.map(([variant, rows]) => (
+      <Fragment>
+        <Pre class={variant}>{BACKLINKS_LAB
EL}</Pre>
+        {rows.map((r) => (
+          <Pre class={variant}>{indent}<a c
lass="backlink-link" href={r.url}>{r.label}<
/a></Pre>
+        ))}
+      </Fragment>
+    ))}
+  </nav>
+)}
 

diff --git a/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.scss b
/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.scss
new file mode 100644
index 0000000..e0dcbf3
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.scss
@@ -0,0 +1,18 @@
+.backlinks {
+  display: block;
+}
+
+.backlink-link {
+  color: var(--ink);
+  text-decoration: none;
+
+  &:hover {
+    text-decoration: underline;
+    text-underline-offset: 3px;
+  }
+
+  &:focus-visible {
+    outline: 1px dashed var(--ink);
+    outline-offset: 1px;
+  }
+}

diff --git a/ooknet-design/src/components/Ar
ticleBacklinks/ArticleBacklinks.scss b/ookne
t-design/src/components/ArticleBacklinks/Art
icleBacklinks.scss
new file mode 100644
index 0000000..e0dcbf3
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBa
cklinks/ArticleBacklinks.scss
@@ -0,0 +1,18 @@
+.backlinks {
+  display: block;
+}
+
+.backlink-link {
+  color: var(--ink);
+  text-decoration: none;
+
+  &:hover {
+    text-decoration: underline;
+    text-underline-offset: 3px;
+  }
+
+  &:focus-visible {
+    outline: 1px dashed var(--ink);
+    outline-offset: 1px;
+  }
+}
 

diff --git a/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.test.t
s b/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.test.ts
new file mode 100644
index 0000000..2d49ccd
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.test.ts
@@ -0,0 +1,28 @@
+import { describe, expect, it } from "vitest";
+import { BACKLINKS_INDENT, layoutBacklinks } from "./ArticleBacklinks";
+import { len } from "../../lib/ascii";
+
+const ref = (label: string) => ({ url: "/notes/x/", label });
+
+describe("layoutBacklinks", () => {
+  it("passes short labels through", () => {
+    const rows = layoutBacklinks([ref("N0 - STYLE SHEET")], 86);
+    expect(rows[0].label).toBe("N0 - STYLE SHEET");
+  });
+
+  it("truncates labels to the available width with an ellipsis", () => {
+    const long = "N218 - COLLECT DATA TO ANALYZE AND TROUBLESHOOT PERFORMANCE SCE
NARIOS";
+    const rows = layoutBacklinks([ref(long)], 48);
+    expect(len(rows[0].label)).toBe(48 - BACKLINKS_INDENT);
+    expect(rows[0].label.endsWith("...")).toBe(true);
+  });
+
+  it("keeps every row on the grid at both frame widths", () => {
+    const refs = [ref("N0 - STYLE SHEET"), ref("DOCS - FRAMES")];
+    for (const w of [86, 48]) {
+      for (const r of layoutBacklinks(refs, w)) {
+        expect(len(r.label)).toBeLessThanOrEqual(w - BACKLINKS_INDENT);
+      }
+    }
+  });
+});

diff --git a/ooknet-design/src/components/Ar
ticleBacklinks/ArticleBacklinks.test.ts b/oo
knet-design/src/components/ArticleBacklinks/
ArticleBacklinks.test.ts
new file mode 100644
index 0000000..2d49ccd
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBa
cklinks/ArticleBacklinks.test.ts
@@ -0,0 +1,28 @@
+import { describe, expect, it } from "vites
t";
+import { BACKLINKS_INDENT, layoutBacklinks 
} from "./ArticleBacklinks";
+import { len } from "../../lib/ascii";
+
+const ref = (label: string) => ({ url: "/no
tes/x/", label });
+
+describe("layoutBacklinks", () => {
+  it("passes short labels through", () => {
+    const rows = layoutBacklinks([ref("N0 -
 STYLE SHEET")], 86);
+    expect(rows[0].label).toBe("N0 - STYLE 
SHEET");
+  });
+
+  it("truncates labels to the available wid
th with an ellipsis", () => {
+    const long = "N218 - COLLECT DATA TO AN
ALYZE AND TROUBLESHOOT PERFORMANCE SCENARIOS
";
+    const rows = layoutBacklinks([ref(long)
], 48);
+    expect(len(rows[0].label)).toBe(48 - BA
CKLINKS_INDENT);
+    expect(rows[0].label.endsWith("...")).t
oBe(true);
+  });
+
+  it("keeps every row on the grid at both f
rame widths", () => {
+    const refs = [ref("N0 - STYLE SHEET"), 
ref("DOCS - FRAMES")];
+    for (const w of [86, 48]) {
+      for (const r of layoutBacklinks(refs,
 w)) {
+        expect(len(r.label)).toBeLessThanOr
Equal(w - BACKLINKS_INDENT);
+      }
+    }
+  });
+});
 

diff --git a/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.ts b/o
oknet-design/src/components/ArticleBacklinks/ArticleBacklinks.ts
new file mode 100644
index 0000000..2d10737
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.ts
@@ -0,0 +1,25 @@
+// REFERENCED BY - pages linking here via wikilinks. Header row plus one
+// linked entry per source, indented to the kv value column so the block
+// sits flush with SEE ALSO / FILED / TAGS.
+import { checkGrid, len } from "../../lib/ascii";
+import type { Backlink } from "../../lib/wikilinks";
+
+export interface Props {
+  refs: Backlink[];
+}
+
+export const BACKLINKS_LABEL = "REFERENCED BY";
+export const BACKLINKS_INDENT = 12;
+
+/** Entries with labels truncated (...) to fit after the indent. */
+export function layoutBacklinks(refs: Backlink[], width: number): Backlink[] {
+  const avail = Math.max(1, width - BACKLINKS_INDENT);
+  return refs.map((r) => {
+    const label =
+      len(r.label) > avail
+        ? [...r.label].slice(0, avail - 1).join("") + "..."
+        : r.label;
+    checkGrid(label, avail, "backlink entry");
+    return { url: r.url, label };
+  });
+}

diff --git a/ooknet-design/src/components/Ar
ticleBacklinks/ArticleBacklinks.ts b/ooknet-
design/src/components/ArticleBacklinks/Artic
leBacklinks.ts
new file mode 100644
index 0000000..2d10737
--- /dev/null
+++ b/ooknet-design/src/components/ArticleBa
cklinks/ArticleBacklinks.ts
@@ -0,0 +1,25 @@
+// REFERENCED BY - pages linking here via w
ikilinks. Header row plus one
+// linked entry per source, indented to the
 kv value column so the block
+// sits flush with SEE ALSO / FILED / TAGS.
+import { checkGrid, len } from "../../lib/a
scii";
+import type { Backlink } from "../../lib/wi
kilinks";
+
+export interface Props {
+  refs: Backlink[];
+}
+
+export const BACKLINKS_LABEL = "REFERENCED 
BY";
+export const BACKLINKS_INDENT = 12;
+
+/** Entries with labels truncated (...) to 
fit after the indent. */
+export function layoutBacklinks(refs: Backl
ink[], width: number): Backlink[] {
+  const avail = Math.max(1, width - BACKLIN
KS_INDENT);
+  return refs.map((r) => {
+    const label =
+      len(r.label) > avail
+        ? [...r.label].slice(0, avail - 1).
join("") + "..."
+        : r.label;
+    checkGrid(label, avail, "backlink entry
");
+    return { url: r.url, label };
+  });
+}
 

diff --git a/ooknet-design/src/components/ArticlePagination/ArticlePagination.astr
o b/ooknet-design/src/components/ArticlePagination/ArticlePagination.astro
index b9e557d..7c45a82 100644
--- a/ooknet-design/src/components/ArticlePagination/ArticlePagination.astro
+++ b/ooknet-design/src/components/ArticlePagination/ArticlePagination.astro
@@ -16,7 +16,7 @@ const gap = (w: number) =>
 ---
 {(prev || next) && (
   <nav class="article-pagination" aria-label="Adjacent notes">
-    <Pre class="frame-wide">{prevLabel && prev && <Button label={prevLabel} href=
{`/notes/${prev.id}/`} />}{" ".repeat(gap(FRAME_W))}{nextLabel && next && <Button 
label={nextLabel} href={`/notes/${next.id}/`} />}</Pre>
-    <Pre class="frame-narrow">{prevLabel && prev && <Button label={prevLabel} hre
f={`/notes/${prev.id}/`} />}{" ".repeat(gap(MOBILE_FRAME_W))}{nextLabel && next &&
 <Button label={nextLabel} href={`/notes/${next.id}/`} />}</Pre>
+    <Pre class="frame-wide">{prevLabel && prev && <Button label={prevLabel} href=
{prev.href} />}{" ".repeat(gap(FRAME_W))}{nextLabel && next && <Button label={next
Label} href={next.href} />}</Pre>
+    <Pre class="frame-narrow">{prevLabel && prev && <Button label={prevLabel} hre
f={prev.href} />}{" ".repeat(gap(MOBILE_FRAME_W))}{nextLabel && next && <Button la
bel={nextLabel} href={next.href} />}</Pre>
   </nav>
 )}

diff --git a/ooknet-design/src/components/Ar
ticlePagination/ArticlePagination.astro b/oo
knet-design/src/components/ArticlePagination
/ArticlePagination.astro
index b9e557d..7c45a82 100644
--- a/ooknet-design/src/components/ArticlePa
gination/ArticlePagination.astro
+++ b/ooknet-design/src/components/ArticlePa
gination/ArticlePagination.astro
@@ -16,7 +16,7 @@ const gap = (w: number) =>
 ---
 {(prev || next) && (
   <nav class="article-pagination" aria-labe
l="Adjacent notes">
-    <Pre class="frame-wide">{prevLabel && p
rev && <Button label={prevLabel} href={`/not
es/${prev.id}/`} />}{" ".repeat(gap(FRAME_W)
)}{nextLabel && next && <Button label={nextL
abel} href={`/notes/${next.id}/`} />}</Pre>
-    <Pre class="frame-narrow">{prevLabel &&
 prev && <Button label={prevLabel} href={`/n
otes/${prev.id}/`} />}{" ".repeat(gap(MOBILE
_FRAME_W))}{nextLabel && next && <Button lab
el={nextLabel} href={`/notes/${next.id}/`} /
>}</Pre>
+    <Pre class="frame-wide">{prevLabel && p
rev && <Button label={prevLabel} href={prev.
href} />}{" ".repeat(gap(FRAME_W))}{nextLabe
l && next && <Button label={nextLabel} href=
{next.href} />}</Pre>
+    <Pre class="frame-narrow">{prevLabel &&
 prev && <Button label={prevLabel} href={pre
v.href} />}{" ".repeat(gap(MOBILE_FRAME_W))}
{nextLabel && next && <Button label={nextLab
el} href={next.href} />}</Pre>
   </nav>
 )}
 

diff --git a/ooknet-design/src/components/ArticlePagination/ArticlePagination.ts b
/ooknet-design/src/components/ArticlePagination/ArticlePagination.ts
index 2136cfe..16a9d34 100644
--- a/ooknet-design/src/components/ArticlePagination/ArticlePagination.ts
+++ b/ooknet-design/src/components/ArticlePagination/ArticlePagination.ts
@@ -1,5 +1,5 @@
 export interface PageLink {
-  id: string;
+  href: string;
   note: string;
 }
 

diff --git a/ooknet-design/src/components/Ar
ticlePagination/ArticlePagination.ts b/ookne
t-design/src/components/ArticlePagination/Ar
ticlePagination.ts
index 2136cfe..16a9d34 100644
--- a/ooknet-design/src/components/ArticlePa
gination/ArticlePagination.ts
+++ b/ooknet-design/src/components/ArticlePa
gination/ArticlePagination.ts
@@ -1,5 +1,5 @@
 export interface PageLink {
-  id: string;
+  href: string;
   note: string;
 }
 
 

diff --git a/ooknet-design/src/content/docs/authoring/wikilinks.md b/ooknet-design
/src/content/docs/authoring/wikilinks.md
index 3e57328..e576a0d 100644
--- a/ooknet-design/src/content/docs/authoring/wikilinks.md
+++ b/ooknet-design/src/content/docs/authoring/wikilinks.md
@@ -34,3 +34,12 @@ wiki tradition is to show the gap.
 
 Anywhere markdown renders: notes, 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/
authoring/wikilinks.md b/ooknet-design/src/c
ontent/docs/authoring/wikilinks.md
index 3e57328..e576a0d 100644
--- a/ooknet-design/src/content/docs/authori
ng/wikilinks.md
+++ b/ooknet-design/src/content/docs/authori
ng/wikilinks.md
@@ -34,3 +34,12 @@ wiki tradition is to show
 the gap.
 
 Anywhere markdown renders: notes, 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/lib/wikilinks.test.ts b/ooknet-design/src/lib/wikil
inks.test.ts
index 7ee1d0f..de7c952 100644
--- a/ooknet-design/src/lib/wikilinks.test.ts
+++ b/ooknet-design/src/lib/wikilinks.test.ts
@@ -1,5 +1,5 @@
 import { describe, expect, it } from "vitest";
-import { buildWikiMap, resolveWikilink } from "./wikilinks";
+import { buildBacklinks, buildWikiMap, extractWikiTargets, resolveWikilink } from
 "./wikilinks";
 import { wikiNodes } from "./remark-wikilinks";
 
 describe("buildWikiMap", () => {
@@ -44,3 +44,49 @@ describe("wikiNodes", () => {
     expect(nodes[0].value).toContain("wikilink-dead");
   });
 });
+
+describe("extractWikiTargets", () => {
+  it("collects targets, dropping aliases", () => {
+    expect(extractWikiTargets("see [[N217]] and [[N0|the sheet]]")).toEqual(["N21
7", "N0"]);
+  });
+
+  it("ignores wikilinks inside inline code and fences", () => {
+    const body = "live [[N217]], literal `[[N999]]`\n```\n[[N1]]\n```\ndone";
+    expect(extractWikiTargets(body)).toEqual(["N217"]);
+  });
+});
+
+describe("buildBacklinks", () => {
+  const bl = buildBacklinks();
+  const urls = (target: string) => (bl.get(target) ?? []).map((r) => r.url);
+
+  it("inverts the graph: pages linking N217 appear on its entry", () => {
+    expect(urls("/notes/n217-entropy-bounds/")).toContain("/notes/n0-style-sheet/
");
+    expect(urls("/notes/n217-entropy-bounds/")).toContain("/docs/authoring/filing
-notes/");
+  });
+
+  it("labels note sources with note number and title", () => {
+    const src = bl
+      .get("/notes/n217-entropy-bounds/")!
+      .find((r) => r.url === "/notes/n0-style-sheet/")!;
+    expect(src.label).toBe("N0 - STYLE SHEET");
+  });
+
+  it("drops self-references", () => {
+    // n0 links [[Style Sheet]] (itself) - must not appear on its own list
+    expect(urls("/notes/n0-style-sheet/")).not.toContain("/notes/n0-style-sheet/"
);
+  });
+
+  it("never lists draft sources", () => {
+    for (const list of bl.values()) {
+      expect(list.map((r) => r.url)).not.toContain("/notes/n1-nixos-thoughts/");
+    }
+  });
+
+  it("sorts note sources before doc sources", () => {
+    for (const list of bl.values()) {
+      const kinds = list.map((r) => (r.url.startsWith("/notes/") ? 0 : 1));
+      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 7ee1d0f..de7c952 100644
--- a/ooknet-design/src/lib/wikilinks.test.t
s
+++ b/ooknet-design/src/lib/wikilinks.test.t
s
@@ -1,5 +1,5 @@
 import { describe, expect, it } from "vites
t";
-import { buildWikiMap, resolveWikilink } fr
om "./wikilinks";
+import { buildBacklinks, buildWikiMap, extr
actWikiTargets, resolveWikilink } from "./wi
kilinks";
 import { wikiNodes } from "./remark-wikilin
ks";
 
 describe("buildWikiMap", () => {
@@ -44,3 +44,49 @@ describe("wikiNodes", () 
=> {
     expect(nodes[0].value).toContain("wikil
ink-dead");
   });
 });
+
+describe("extractWikiTargets", () => {
+  it("collects targets, dropping aliases", 
() => {
+    expect(extractWikiTargets("see [[N217]]
 and [[N0|the sheet]]")).toEqual(["N217", "N
0"]);
+  });
+
+  it("ignores wikilinks inside inline code 
and fences", () => {
+    const body = "live [[N217]], literal `[
[N999]]`\n```\n[[N1]]\n```\ndone";
+    expect(extractWikiTargets(body)).toEqua
l(["N217"]);
+  });
+});
+
+describe("buildBacklinks", () => {
+  const bl = buildBacklinks();
+  const urls = (target: string) => (bl.get(
target) ?? []).map((r) => r.url);
+
+  it("inverts the graph: pages linking N217
 appear on its entry", () => {
+    expect(urls("/notes/n217-entropy-bounds
/")).toContain("/notes/n0-style-sheet/");
+    expect(urls("/notes/n217-entropy-bounds
/")).toContain("/docs/authoring/filing-notes
/");
+  });
+
+  it("labels note sources with note number 
and title", () => {
+    const src = bl
+      .get("/notes/n217-entropy-bounds/")!
+      .find((r) => r.url === "/notes/n0-sty
le-sheet/")!;
+    expect(src.label).toBe("N0 - STYLE SHEE
T");
+  });
+
+  it("drops self-references", () => {
+    // n0 links [[Style Sheet]] (itself) - 
must not appear on its own list
+    expect(urls("/notes/n0-style-sheet/")).
not.toContain("/notes/n0-style-sheet/");
+  });
+
+  it("never lists draft sources", () => {
+    for (const list of bl.values()) {
+      expect(list.map((r) => r.url)).not.to
Contain("/notes/n1-nixos-thoughts/");
+    }
+  });
+
+  it("sorts note sources before doc sources
", () => {
+    for (const list of bl.values()) {
+      const kinds = list.map((r) => (r.url.
startsWith("/notes/") ? 0 : 1));
+      expect(kinds).toEqual([...kinds].sort
());
+    }
+  });
+});
 

diff --git a/ooknet-design/src/lib/wikilinks.ts b/ooknet-design/src/lib/wikilinks.
ts
index eecda2d..d041d78 100644
--- a/ooknet-design/src/lib/wikilinks.ts
+++ b/ooknet-design/src/lib/wikilinks.ts
@@ -53,3 +53,79 @@ export function buildWikiMap(contentDir = "src/content"): WikiM
ap {
 export function resolveWikilink(target: string, map: WikiMap): string | null {
   return map.get(target.trim().toLowerCase()) ?? null;
 }
+
+// ── Backlinks ────────────────────────────────────────────────────────
+// The same graph, inverted: for each page, which pages link to it.
+
+export interface Backlink {
+  url: string;
+  label: string;
+}
+
+const WIKI_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
+
+/** Wikilink targets in a markdown body. Code is stripped first so
+ *  literal `[[syntax]]` in docs doesn't count as a reference - mirrors
+ *  the remark plugin, which never sees code as text nodes. */
+export function extractWikiTargets(body: string): string[] {
+  const stripped = body
+    .replace(/^(?:```|~~~)[\s\S]*?^(?:```|~~~)/gm, "")
+    .replace(/`[^`\n]+`/g, "");
+  return [...stripped.matchAll(WIKI_RE)].map((m) => m[1].trim());
+}
+
+function scanSources(
+  out: Map<string, Backlink[]>,
+  map: WikiMap,
+  dir: string,
+  urlFor: (id: string) => string,
+  labelFor: (id: string, fm: string) => string,
+) {
+  let files: string[];
+  try {
+    files = mdFiles(dir);
+  } catch {
+    return;
+  }
+  for (const file of files) {
+    const id = file.replace(/\.md$/, "");
+    const raw = readFileSync(join(dir, file), "utf8");
+    const fmMatch = raw.match(FM_RE);
+    const fm = fmMatch?.[1] ?? "";
+    if (fmField(fm, "draft") === "true") continue;
+    const srcUrl = urlFor(id);
+    const targets = new Set<string>();
+    for (const t of extractWikiTargets(raw.slice(fmMatch?.[0].length ?? 0))) {
+      const url = resolveWikilink(t, map);
+      if (url && url !== srcUrl) targets.add(url);
+    }
+    for (const target of targets) {
+      if (!out.has(target)) out.set(target, []);
+      out.get(target)!.push({ url: srcUrl, label: labelFor(id, fm) });
+    }
+  }
+}
+
+/** Target url ? pages whose body links to it. Dead links and
+ *  self-references are dropped; notes sort before docs. */
+export function buildBacklinks(contentDir = "src/content"): Map<string, Backlink[
]> {
+  const map = buildWikiMap(contentDir);
+  const out = new Map<string, Backlink[]>();
+  scanSources(out, map, join(contentDir, "notes"), (id) => `/notes/${id}/`, (id, 
fm) => {
+    const note = fmField(fm, "note");
+    const title = fmField(fm, "title") ?? id.toUpperCase();
+    return note ? `${note} - ${title}` : title;
+  });
+  scanSources(out, map, join(contentDir, "docs"), (id) => `/docs/${id}/`, (id, fm
) => {
+    return `DOCS - ${(fmField(fm, "title") ?? id).toUpperCase()}`;
+  });
+  const group = (u: string) => (u.startsWith("/notes/") ? 0 : 1);
+  for (const list of out.values()) {
+    list.sort(
+      (a, b) =>
+        group(a.url) - group(b.url) ||
+        a.label.localeCompare(b.label, "en", { numeric: true }),
+    );
+  }
+  return out;
+}

diff --git a/ooknet-design/src/lib/wikilinks
.ts b/ooknet-design/src/lib/wikilinks.ts
index eecda2d..d041d78 100644
--- a/ooknet-design/src/lib/wikilinks.ts
+++ b/ooknet-design/src/lib/wikilinks.ts
@@ -53,3 +53,79 @@ export function buildWiki
Map(contentDir = "src/content"): WikiMap {
 export function resolveWikilink(target: str
ing, map: WikiMap): string | null {
   return map.get(target.trim().toLowerCase(
)) ?? null;
 }
+
+// ── Backlinks ───────────────────────────
─────────────────────────────
+// The same graph, inverted: for each page,
 which pages link to it.
+
+export interface Backlink {
+  url: string;
+  label: string;
+}
+
+const WIKI_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?
\]\]/g;
+
+/** Wikilink targets in a markdown body. Co
de is stripped first so
+ *  literal `[[syntax]]` in docs doesn't co
unt as a reference - mirrors
+ *  the remark plugin, which never sees cod
e as text nodes. */
+export function extractWikiTargets(body: st
ring): string[] {
+  const stripped = body
+    .replace(/^(?:```|~~~)[\s\S]*?^(?:```|~
~~)/gm, "")
+    .replace(/`[^`\n]+`/g, "");
+  return [...stripped.matchAll(WIKI_RE)].ma
p((m) => m[1].trim());
+}
+
+function scanSources(
+  out: Map<string, Backlink[]>,
+  map: WikiMap,
+  dir: string,
+  urlFor: (id: string) => string,
+  labelFor: (id: string, fm: string) => str
ing,
+) {
+  let files: string[];
+  try {
+    files = mdFiles(dir);
+  } catch {
+    return;
+  }
+  for (const file of files) {
+    const id = file.replace(/\.md$/, "");
+    const raw = readFileSync(join(dir, file
), "utf8");
+    const fmMatch = raw.match(FM_RE);
+    const fm = fmMatch?.[1] ?? "";
+    if (fmField(fm, "draft") === "true") co
ntinue;
+    const srcUrl = urlFor(id);
+    const targets = new Set<string>();
+    for (const t of extractWikiTargets(raw.
slice(fmMatch?.[0].length ?? 0))) {
+      const url = resolveWikilink(t, map);
+      if (url && url !== srcUrl) targets.ad
d(url);
+    }
+    for (const target of targets) {
+      if (!out.has(target)) out.set(target,
 []);
+      out.get(target)!.push({ url: srcUrl, 
label: labelFor(id, fm) });
+    }
+  }
+}
+
+/** Target url ? pages whose body links to 
it. Dead links and
+ *  self-references are dropped; notes sort
 before docs. */
+export function buildBacklinks(contentDir =
 "src/content"): Map<string, Backlink[]> {
+  const map = buildWikiMap(contentDir);
+  const out = new Map<string, Backlink[]>()
;
+  scanSources(out, map, join(contentDir, "n
otes"), (id) => `/notes/${id}/`, (id, fm) =>
 {
+    const note = fmField(fm, "note");
+    const title = fmField(fm, "title") ?? i
d.toUpperCase();
+    return note ? `${note} - ${title}` : ti
tle;
+  });
+  scanSources(out, map, join(contentDir, "d
ocs"), (id) => `/docs/${id}/`, (id, fm) => {
+    return `DOCS - ${(fmField(fm, "title") 
?? id).toUpperCase()}`;
+  });
+  const group = (u: string) => (u.startsWit
h("/notes/") ? 0 : 1);
+  for (const list of out.values()) {
+    list.sort(
+      (a, b) =>
+        group(a.url) - group(b.url) ||
+        a.label.localeCompare(b.label, "en"
, { numeric: true }),
+    );
+  }
+  return out;
+}
 

diff --git a/ooknet-design/src/pages/docs/[...slug].astro b/ooknet-design/src/page
s/docs/[...slug].astro
index c4e1332..97c33ee 100644
--- a/ooknet-design/src/pages/docs/[...slug].astro
+++ b/ooknet-design/src/pages/docs/[...slug].astro
@@ -10,12 +10,15 @@ import Prose from "../../components/Prose/Prose.astro";
 import ArticlePagination from "../../components/ArticlePagination/ArticlePaginati
on.astro";
 import type { PageLink } from "../../components/ArticlePagination/ArticlePaginati
on";
 import DocsNav from "../../components/DocsNav/DocsNav.astro";
+import ArticleBacklinks from "../../components/ArticleBacklinks/ArticleBacklinks.
astro";
 import { box, center, len, rule, ruleD, row2, wrap } from "../../lib/ascii";
 import { published } from "../../lib/notes";
 import { docsOrder, type DocEntry } from "../../lib/docs";
+import { buildBacklinks, type Backlink } from "../../lib/wikilinks";
 
 export async function getStaticPaths() {
   const docs = await getCollection("docs", published);
+  const backlinks = buildBacklinks();
   const ordered = docsOrder(
     docs.map((d) => ({ id: d.id, title: d.data.title, order: d.data.order })),
   );
@@ -27,7 +30,7 @@ export async function getStaticPaths() {
     if (!id) return null;
     const title = byId.get(id)!.data.title.toUpperCase();
     const note = len(title) > 18 ? [...title].slice(0, 17).join("") + "..." : tit
le;
-    return { id, note };
+    return { href: `/docs/${id}/`, note };
   };
 
   return ordered.map((d, i) => ({
@@ -37,6 +40,7 @@ export async function getStaticPaths() {
       prev: link(ordered[i - 1]?.id),
       next: link(ordered[i + 1]?.id),
       all: ordered,
+      refs: backlinks.get(`/docs/${d.id}/`) ?? [],
     },
   }));
 }
@@ -46,8 +50,9 @@ interface Props {
   prev: PageLink | null;
   next: PageLink | null;
   all: DocEntry[];
+  refs: Backlink[];
 }
-const { doc, prev, next, all } = Astro.props as Props;
+const { doc, prev, next, all, refs } = Astro.props as Props;
 const { Content, headings } = await render(doc);
 
 const buildTitleCard = (w: number) => {
@@ -71,6 +76,12 @@ const buildTitleCard = (w: number) => {
   <DocsNav docs={all} currentId={doc.id} />
   {headings.length >= 3 && <TableOfContents headings={headings} />}
   <Prose><Content /></Prose>
+  {refs.length > 0 && (
+    <Fragment>
+      <Frame build={(w) => rule(w)} />
+      <ArticleBacklinks refs={refs} />
+    </Fragment>
+  )}
   <Pre>{" "}</Pre>
   <ArticlePagination prev={prev} next={next} />
   <Frame build={(w) => rule(w)} />

diff --git a/ooknet-design/src/pages/docs/[.
..slug].astro b/ooknet-design/src/pages/docs
/[...slug].astro
index c4e1332..97c33ee 100644
--- a/ooknet-design/src/pages/docs/[...slug]
.astro
+++ b/ooknet-design/src/pages/docs/[...slug]
.astro
@@ -10,12 +10,15 @@ import Prose from "../..
/components/Prose/Prose.astro";
 import ArticlePagination from "../../compon
ents/ArticlePagination/ArticlePagination.ast
ro";
 import type { PageLink } from "../../compon
ents/ArticlePagination/ArticlePagination";
 import DocsNav from "../../components/DocsN
av/DocsNav.astro";
+import ArticleBacklinks from "../../compone
nts/ArticleBacklinks/ArticleBacklinks.astro"
;
 import { box, center, len, rule, ruleD, row
2, wrap } from "../../lib/ascii";
 import { published } from "../../lib/notes"
;
 import { docsOrder, type DocEntry } from ".
./../lib/docs";
+import { buildBacklinks, type Backlink } fr
om "../../lib/wikilinks";
 
 export async function getStaticPaths() {
   const docs = await getCollection("docs", 
published);
+  const backlinks = buildBacklinks();
   const ordered = docsOrder(
     docs.map((d) => ({ id: d.id, title: d.d
ata.title, order: d.data.order })),
   );
@@ -27,7 +30,7 @@ export async function getS
taticPaths() {
     if (!id) return null;
     const title = byId.get(id)!.data.title.
toUpperCase();
     const note = len(title) > 18 ? [...titl
e].slice(0, 17).join("") + "..." : title;
-    return { id, note };
+    return { href: `/docs/${id}/`, note };
   };
 
   return ordered.map((d, i) => ({
@@ -37,6 +40,7 @@ export async function getS
taticPaths() {
       prev: link(ordered[i - 1]?.id),
       next: link(ordered[i + 1]?.id),
       all: ordered,
+      refs: backlinks.get(`/docs/${d.id}/`)
 ?? [],
     },
   }));
 }
@@ -46,8 +50,9 @@ interface Props {
   prev: PageLink | null;
   next: PageLink | null;
   all: DocEntry[];
+  refs: Backlink[];
 }
-const { doc, prev, next, all } = Astro.prop
s as Props;
+const { doc, prev, next, all, refs } = Astr
o.props as Props;
 const { Content, headings } = await render(
doc);
 
 const buildTitleCard = (w: number) => {
@@ -71,6 +76,12 @@ const buildTitleCard = (w
: number) => {
   <DocsNav docs={all} currentId={doc.id} />
   {headings.length >= 3 && <TableOfContents
 headings={headings} />}
   <Prose><Content /></Prose>
+  {refs.length > 0 && (
+    <Fragment>
+      <Frame build={(w) => rule(w)} />
+      <ArticleBacklinks refs={refs} />
+    </Fragment>
+  )}
   <Pre>{" "}</Pre>
   <ArticlePagination prev={prev} next={next
} />
   <Frame build={(w) => rule(w)} />
 

diff --git a/ooknet-design/src/pages/notes/[slug].astro b/ooknet-design/src/pages/
notes/[slug].astro
index b7c1968..fe783ff 100644
--- a/ooknet-design/src/pages/notes/[slug].astro
+++ b/ooknet-design/src/pages/notes/[slug].astro
@@ -13,16 +13,24 @@ import ArticlePagination from "../../components/ArticlePaginat
ion/ArticlePaginat
 import ArticleFooter from "../../components/ArticleFooter/ArticleFooter.astro";
 import Prose from "../../components/Prose/Prose.astro";
 import Pre from "../../components/Pre/Pre.astro";
+import ArticleBacklinks from "../../components/ArticleBacklinks/ArticleBacklinks.
astro";
 import type { PageLink } from "../../components/ArticlePagination/ArticlePaginati
on";
 import { published, byNoteNum } from "../../lib/notes";
+import { buildBacklinks, type Backlink } from "../../lib/wikilinks";
 
 export async function getStaticPaths() {
   const notes = (await getCollection("notes", published)).sort(byNoteNum);
+  const backlinks = buildBacklinks();
   const link = (n?: (typeof notes)[number]): PageLink | null =>
-    n ? { id: n.id, note: n.data.note } : null;
+    n ? { href: `/notes/${n.id}/`, note: n.data.note } : null;
   return notes.map((n, i) => ({
     params: { slug: n.id },
-    props: { note: n, prev: link(notes[i - 1]), next: link(notes[i + 1]) },
+    props: {
+      note: n,
+      prev: link(notes[i - 1]),
+      next: link(notes[i + 1]),
+      refs: backlinks.get(`/notes/${n.id}/`) ?? [],
+    },
   }));
 }
 
@@ -30,8 +38,9 @@ interface Props {
   note: CollectionEntry<"notes">;
   prev: PageLink | null;
   next: PageLink | null;
+  refs: Backlink[];
 }
-const { note, prev, next } = Astro.props as Props;
+const { note, prev, next, refs } = Astro.props as Props;
 const { Content, headings } = await render(note);
 ---
 <ArticleLayout title={note.data.title}>
@@ -43,6 +52,7 @@ const { Content, headings } = await render(note);
   {headings.length >= 3 && <TableOfContents headings={headings} />}
   <Prose><Content /></Prose>
   <ArticleSeeAlso fm={note.data} />
+  <ArticleBacklinks refs={refs} />
   <ArticleTags fm={note.data} />
   <Pre>{" "}</Pre>
   <ArticlePagination prev={prev} next={next} />

diff --git a/ooknet-design/src/pages/notes/[
slug].astro b/ooknet-design/src/pages/notes/
[slug].astro
index b7c1968..fe783ff 100644
--- a/ooknet-design/src/pages/notes/[slug].a
stro
+++ b/ooknet-design/src/pages/notes/[slug].a
stro
@@ -13,16 +13,24 @@ import ArticlePagination
 from "../../components/ArticlePagination/Ar
ticlePaginat
 import ArticleFooter from "../../components
/ArticleFooter/ArticleFooter.astro";
 import Prose from "../../components/Prose/P
rose.astro";
 import Pre from "../../components/Pre/Pre.a
stro";
+import ArticleBacklinks from "../../compone
nts/ArticleBacklinks/ArticleBacklinks.astro"
;
 import type { PageLink } from "../../compon
ents/ArticlePagination/ArticlePagination";
 import { published, byNoteNum } from "../..
/lib/notes";
+import { buildBacklinks, type Backlink } fr
om "../../lib/wikilinks";
 
 export async function getStaticPaths() {
   const notes = (await getCollection("notes
", published)).sort(byNoteNum);
+  const backlinks = buildBacklinks();
   const link = (n?: (typeof notes)[number])
: PageLink | null =>
-    n ? { id: n.id, note: n.data.note } : n
ull;
+    n ? { href: `/notes/${n.id}/`, note: n.
data.note } : null;
   return notes.map((n, i) => ({
     params: { slug: n.id },
-    props: { note: n, prev: link(notes[i - 
1]), next: link(notes[i + 1]) },
+    props: {
+      note: n,
+      prev: link(notes[i - 1]),
+      next: link(notes[i + 1]),
+      refs: backlinks.get(`/notes/${n.id}/`
) ?? [],
+    },
   }));
 }
 
@@ -30,8 +38,9 @@ interface Props {
   note: CollectionEntry<"notes">;
   prev: PageLink | null;
   next: PageLink | null;
+  refs: Backlink[];
 }
-const { note, prev, next } = Astro.props as
 Props;
+const { note, prev, next, refs } = Astro.pr
ops as Props;
 const { Content, headings } = await render(
note);
 ---
 <ArticleLayout title={note.data.title}>
@@ -43,6 +52,7 @@ const { Content, headings 
} = await render(note);
   {headings.length >= 3 && <TableOfContents
 headings={headings} />}
   <Prose><Content /></Prose>
   <ArticleSeeAlso fm={note.data} />
+  <ArticleBacklinks refs={refs} />
   <ArticleTags fm={note.data} />
   <Pre>{" "}</Pre>
   <ArticlePagination prev={prev} next={next
} />
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET