epistemic redo

This commit is contained in:
Levi Neuwirth 2026-04-11 19:40:58 -04:00
parent b80fe2fee7
commit d113671e96
8 changed files with 111 additions and 118 deletions

View File

@ -59,7 +59,7 @@ no-collapse: true # optional; disables collapsible h2/h3 sections
js: scripts/my-widget.js # optional; per-page JS file (see Page scripts) js: scripts/my-widget.js # optional; per-page JS file (see Page scripts)
# js: [scripts/a.js, scripts/b.js] # or a list # js: [scripts/a.js, scripts/b.js] # or a list
# Epistemic Effort — all optional; the entire section is hidden unless `status` is set # Epistemic profile — all optional; the entire section is hidden unless `status` is set
status: "Working model" # Draft | Working model | Durable | Refined | Superseded | Deprecated status: "Working model" # Draft | Working model | Durable | Refined | Superseded | Deprecated
confidence: 72 # 0100 integer (%) confidence: 72 # 0100 integer (%)
importance: 3 # 15 integer (rendered as filled/empty dots ●●●○○) importance: 3 # 15 integer (rendered as filled/empty dots ●●●○○)
@ -648,19 +648,26 @@ copies all JS files from `content/` to `_site/` automatically.
The epistemic footer section appears when `status` is set. All other fields The epistemic footer section appears when `status` is set. All other fields
are optional and are shown or hidden independently. are optional and are shown or hidden independently.
| Field | Compact display | Expanded (`<details>`) | | Field | Compact rows | Expanded `<dl>` |
|-------|----------------|------------------------| |-------|-------------|------------------|
| `status` | chip (always shown if present) | — | | *(trust score)* | bordered chip + small-caps "trust" label on the primary row | — |
| `confidence` | `72%` | — | | `status` | small-caps chip on the primary row, alongside the trust chip | — |
| `importance` | `●●●○○` | — | | `confidence` | `· 72% confidence` | — |
| `evidence` | `●●○○○` | — | | `importance` | `· ●●●○○ importance` | — |
| `evidence` | `· ●●○○○ evidence quality` | — |
| `scope` | `· average scope` (if set) | — |
| `novelty` | `· moderate novelty` (if set) | — |
| `practicality` | `· moderate practicality` (if set) | — |
| `stability` | — | auto-computed from git history | | `stability` | — | auto-computed from git history |
| `scope` | — | if set |
| `novelty` | — | if set |
| `practicality` | — | if set |
| `last-reviewed` | — | most recent commit date | | `last-reviewed` | — | most recent commit date |
| `confidence-trend` | — | ↑/↓/→ from last two `confidence-history` entries | | `confidence-trend` | — | ↑/↓/→ from last two `confidence-history` entries |
The **trust score** is auto-computed as `confidence × 0.6 + ((evidence 1) / 4) × 0.4`,
clamped 0100. It is deliberately narrow: it answers "how much should you trust the
central claim?" and nothing else. Importance, scope, novelty, and practicality are
*not* folded in — they are orientation signals shown alongside the chip so a high
trust score on a personal essay cannot be misread as broad significance.
**Stability** is auto-computed from `git log --follow` at every build. The **Stability** is auto-computed from `git log --follow` at every build. The
heuristic: ≤1 commits or age <14 days *volatile*; 5 commits and age <90 heuristic: ≤1 commits or age <14 days *volatile*; 5 commits and age <90
days → *revising*; ≤15 commits or age <365 days *fairly stable*; 30 days → *revising*; ≤15 commits or age <365 days *fairly stable*; 30

View File

@ -306,13 +306,25 @@ confidenceTrendField = field "confidence-trend" $ \item -> do
lastTwo [a, b] = Just (a, b) lastTwo [a, b] = Just (a, b)
lastTwo (_ : rest) = lastTwo rest lastTwo (_ : rest) = lastTwo rest
-- | @$overall-score$@: weighted composite of confidence (50 %), -- | @$overall-score$@: weighted composite of confidence (60 %) and
-- evidence quality (30 %), and importance (20 %), expressed as an -- evidence quality (40 %), expressed as an integer on a 0100 scale.
-- integer on a 0100 scale. --
-- Returns @noResult@ when any contributing field is absent, so -- Importance is intentionally excluded from the score: it answers
-- "should you read this?", not "should you trust it?", and folding
-- the two together inflated the number and muddied its meaning.
-- It still appears in the footer as an independent orientation
-- signal — just not as a credibility input.
--
-- The 15 evidence scale is rescaled as @(ev 1) / 4@ rather than
-- plain @ev / 5@. The naive form left a hidden +6 floor (since
-- @1/5 = 0.2@) and a midpoint of 0.6 instead of 0.5; the rescale
-- makes evidence=1 contribute zero and evidence=3 contribute exactly
-- half, so a "true midpoint" entry (conf=50, ev=3) lands on 50.
--
-- Returns @noResult@ when confidence or evidence is absent, so
-- @$if(overall-score)$@ guards the template safely. -- @$if(overall-score)$@ guards the template safely.
-- --
-- Formula: raw = conf/100·0.5 + ev/5·0.3 + imp/5·0.2 (01) -- Formula: raw = conf/100 · 0.6 + (ev 1)/4 · 0.4 (01)
-- score = clamp₀₋₁₀₀(round(raw · 100)) -- score = clamp₀₋₁₀₀(round(raw · 100))
overallScoreField :: Context String overallScoreField :: Context String
overallScoreField = field "overall-score" $ \item -> do overallScoreField = field "overall-score" $ \item -> do
@ -320,16 +332,14 @@ overallScoreField = field "overall-score" $ \item -> do
let readInt s = readMaybe s :: Maybe Int let readInt s = readMaybe s :: Maybe Int
case ( readInt =<< lookupString "confidence" meta case ( readInt =<< lookupString "confidence" meta
, readInt =<< lookupString "evidence" meta , readInt =<< lookupString "evidence" meta
, readInt =<< lookupString "importance" meta
) of ) of
(Just conf, Just ev, Just imp) -> (Just conf, Just ev) ->
let raw :: Double let raw :: Double
raw = fromIntegral conf / 100.0 * 0.5 raw = fromIntegral conf / 100.0 * 0.6
+ fromIntegral ev / 5.0 * 0.3 + fromIntegral (ev - 1) / 4.0 * 0.4
+ fromIntegral imp / 5.0 * 0.2
score = max 0 (min 100 (round (raw * 100.0) :: Int)) score = max 0 (min 100 (round (raw * 100.0) :: Int))
in return (show score) in return (show score)
_ -> fail "overall-score: confidence, evidence, or importance not set" _ -> fail "overall-score: confidence or evidence not set"
-- | All epistemic context fields composed. -- | All epistemic context fields composed.
epistemicCtx :: Context String epistemicCtx :: Context String

View File

@ -145,7 +145,7 @@ Auto-derived at build time: `stability` (from `git log --follow`), `last-reviewe
**Bottom metadata footer:** **Bottom metadata footer:**
- **Version history** — three-tier priority: (1) frontmatter `history` list with authored notes → (2) git log dates (date-only) → (3) `date-created` / `date-modified` fallback. `make build` auto-commits `content/` before building, keeping git history current. - **Version history** — three-tier priority: (1) frontmatter `history` list with authored notes → (2) git log dates (date-only) → (3) `date-created` / `date-modified` fallback. `make build` auto-commits `content/` before building, keeping git history current.
- **Epistemic** (if `status` set) — compact: status chip · confidence % · importance dots · evidence dots; expanded `<details>`: stability · scope · novelty · practicality · last reviewed · confidence trend - **Epistemic** (if `status` set) — primary row: trust-score chip + status; labeled rows beneath: confidence · importance · evidence · scope · novelty · practicality (each shown only if its frontmatter field is set); always-visible `<dl>`: stability · last reviewed · confidence trend
- **Bibliography** — formatted citations + Further Reading - **Bibliography** — formatted citations + Further Reading
- **Backlinks** — auto-generated; each entry shows source title (link) + collapsible context paragraph - **Backlinks** — auto-generated; each entry shows source title (link) + collapsible context paragraph
@ -648,6 +648,7 @@ Implemented across `build/Stability.hs`, `build/Contexts.hs`, `templates/partial
| `$confidence$` | frontmatter `confidence` | via `defaultContext` | | `$confidence$` | frontmatter `confidence` | via `defaultContext` |
| `$importance-dots$` | frontmatter `importance` (15) | `●●●○○` rendered in Haskell | | `$importance-dots$` | frontmatter `importance` (15) | `●●●○○` rendered in Haskell |
| `$evidence-dots$` | frontmatter `evidence` (15) | same | | `$evidence-dots$` | frontmatter `evidence` (15) | same |
| `$overall-score$` | computed from `confidence` + `evidence` | trust score: `conf/100 · 0.6 + (ev1)/4 · 0.4`, clamped 0100. Importance/scope/novelty/practicality intentionally excluded — see colophon "Living Documents" for the rationale. |
| `$confidence-trend$` | frontmatter `confidence-history` list | ↑ / ↓ / → from last two entries | | `$confidence-trend$` | frontmatter `confidence-history` list | ↑ / ↓ / → from last two entries |
| `$stability$` | auto-computed via `git log --follow` | always resolves; never fails | | `$stability$` | auto-computed via `git log --follow` | always resolves; never fails |
| `$last-reviewed$` | most recent commit date | formatted "%-d %B %Y"; `noResult` if no commits | | `$last-reviewed$` | most recent commit date | formatted "%-d %B %Y"; `noResult` if no commits |

View File

@ -627,15 +627,6 @@ nav.site-nav {
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
} }
/* When the score box is present, suppress the link-level underline and
restore it only on the label text leaves the gap and box undecorated. */
.meta-pagelinks a:has(.meta-overall-score):hover {
text-decoration: none;
}
.meta-pagelinks a:has(.meta-overall-score):hover .ep-link-label {
text-decoration: underline;
text-underline-offset: 2px;
}
.meta-pagelinks a + a::before { .meta-pagelinks a + a::before {
content: " · "; content: " · ";
color: var(--border); color: var(--border);
@ -644,6 +635,19 @@ nav.site-nav {
white-space: pre; white-space: pre;
} }
/* Top-of-page parallel tag strip surfaces the full epistemic profile
inline so a reader sees status, trust score, and every orientation
axis at a glance, without scrolling to the footer. The trust chip
anchors the left edge; status sits next to it; the labeled rows
follow with their leading centerdot separators. Wraps freely. */
.meta-epistemic-strip {
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: center;
gap: 0.35rem 0.55rem;
}
/* ============================================================ /* ============================================================
BOTTOM METADATA FOOTER BOTTOM METADATA FOOTER
@ -808,15 +812,6 @@ details[open] > .backlink-summary::before {
with an expandable <details> for the full profile. with an expandable <details> for the full profile.
============================================================ */ ============================================================ */
/* Compact row — chips laid out inline */
.ep-compact {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.35rem 0.55rem;
margin-bottom: 0.5rem;
}
/* Status chip */ /* Status chip */
.ep-status { .ep-status {
font-family: var(--font-sans); font-family: var(--font-sans);
@ -826,15 +821,18 @@ details[open] > .backlink-summary::before {
color: var(--text-muted); color: var(--text-muted);
} }
/* Confidence percentage */ /* Every labeled row beneath the primary line shares one class so new
.ep-confidence { epistemic fields can be added in the template without touching CSS.
Used by confidence, importance, evidence, scope, novelty, practicality. */
.ep-row {
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 0.72rem; font-size: 0.72rem;
color: var(--text-faint); color: var(--text-faint);
} }
.ep-confidence::before { content: "·\00a0"; color: var(--border); } .ep-row::before { content: "·\00a0"; color: var(--border); }
/* Dot scale (importance / evidence) */ /* Dot scale (importance / evidence) only ever used nested inside an
.ep-row wrapper, which already supplies the centerdot separator. */
.ep-dots { .ep-dots {
font-size: 0.55rem; font-size: 0.55rem;
letter-spacing: 0.12em; letter-spacing: 0.12em;
@ -842,34 +840,30 @@ details[open] > .backlink-summary::before {
line-height: 1; line-height: 1;
vertical-align: 0.15em; vertical-align: 0.15em;
} }
.ep-dots::before { content: "·\00a0"; color: var(--border); font-size: 0.72rem; vertical-align: 0; } .ep-row .ep-dots::before { content: none; }
/* Expandable detail block */ /* Trust chip on the primary row bordered numeric chip plus a small-caps
.ep-details { "trust" label, mirroring .ep-status typography so the two sit together. */
margin-top: 0.1rem; .ep-trust {
}
.ep-summary {
font-size: 0.68rem;
font-family: var(--font-sans); font-family: var(--font-sans);
color: var(--text-faint); font-size: 0.72rem;
cursor: pointer; font-variant-caps: all-small-caps;
list-style: none; letter-spacing: 0.05em;
user-select: none; color: var(--text-muted);
} }
.ep-score {
.ep-summary::-webkit-details-marker { display: none; } font-family: var(--font-sans);
font-size: 0.72rem;
.ep-summary::before { font-variant-caps: normal;
content: "▸\00a0"; font-variant-numeric: tabular-nums;
font-size: 0.6em; letter-spacing: 0.03em;
vertical-align: 0.1em; color: var(--text-muted);
border: 1px solid var(--border-muted);
border-radius: 2px;
padding: 0.05em 0.5em;
margin-right: 0.25em;
} }
details[open] > .ep-summary::before { content: "▾\00a0"; }
.ep-summary:hover { color: var(--text-muted); }
/* Definition list for expanded profile */ /* Definition list for expanded profile */
.ep-expanded { .ep-expanded {
display: grid; display: grid;
@ -894,24 +888,6 @@ details[open] > .ep-summary::before { content: "▾\00a0"; }
} }
/* Overall score percentage in the top metadata nav (adjacent to Epistemic link).
Renders as a compact numeric chip: "72%" in small-caps sans, separated from
the link text by a faint centerdot. */
.meta-overall-score {
font-family: var(--font-sans);
font-size: 0.68rem;
font-variant-numeric: tabular-nums;
letter-spacing: 0.03em;
color: var(--text-muted);
display: inline-block;
text-decoration: none;
border: 1px solid var(--border-muted);
border-radius: 2px;
padding: 0.05em 0.5em;
margin-left: 0.5em;
vertical-align: 0.05em;
}
/* ============================================================ /* ============================================================
PAGINATION NAV PAGINATION NAV
============================================================ */ ============================================================ */

View File

@ -641,9 +641,12 @@
return html; return html;
} }
/* Epistemic jump link reads the #epistemic section already in the DOM /* Epistemic jump link pulls the parallel-tag strip from the top of
and renders a compact summary: status/confidence/dots + expanded DL. the page (which holds the author-declared orientation tags) and
All ep-* classes are already styled via components.css. the expanded DL from the #epistemic footer section (which holds
the git-derived stability/last-reviewed/trend). The popup combines
both so a reader hovering the link mid-page sees the full profile
without scrolling.
Returns a DocumentFragment instead of an HTML string so the popup Returns a DocumentFragment instead of an HTML string so the popup
receives cloned nodes (defense in depth if a future change ever receives cloned nodes (defense in depth if a future change ever
@ -651,25 +654,21 @@
would still see exactly the same already-rendered DOM rather than would still see exactly the same already-rendered DOM rather than
a re-parsed string). */ a re-parsed string). */
function epistemicContent() { function epistemicContent() {
var section = document.getElementById('epistemic');
if (!section) return Promise.resolve(null);
var wrap = document.createElement('div'); var wrap = document.createElement('div');
wrap.className = 'popup-epistemic'; wrap.className = 'popup-epistemic';
var compact = section.querySelector('.ep-compact'); var strip = document.querySelector('.meta-epistemic-strip');
if (compact) { if (strip) {
var compactClone = compact.cloneNode(true); wrap.appendChild(strip.cloneNode(true));
wrap.appendChild(compactClone);
} }
var expanded = section.querySelector('.ep-expanded'); var section = document.getElementById('epistemic');
var expanded = section ? section.querySelector('.ep-expanded') : null;
if (expanded) { if (expanded) {
var expandedClone = expanded.cloneNode(true); wrap.appendChild(expanded.cloneNode(true));
wrap.appendChild(expandedClone);
} }
if (!compact && !expanded) return Promise.resolve(null); if (!strip && !expanded) return Promise.resolve(null);
return Promise.resolve(wrap); return Promise.resolve(wrap);
} }

View File

@ -7,7 +7,7 @@
<div class="library-controls-options" role="group" aria-label="Sort order"> <div class="library-controls-options" role="group" aria-label="Sort order">
<button class="library-sort-btn" data-sort="date">date</button> <button class="library-sort-btn" data-sort="date">date</button>
<button class="library-sort-btn" data-sort="title">title</button> <button class="library-sort-btn" data-sort="title">title</button>
<button class="library-sort-btn" data-sort="score">epistemic effort</button> <button class="library-sort-btn" data-sort="score">trust</button>
</div> </div>
</div> </div>

View File

@ -17,9 +17,21 @@
$for(affiliation-links)$$if(affiliation-url)$<a href="$affiliation-url$">$affiliation-name$</a>$else$$affiliation-name$$endif$$sep$ · $endfor$ $for(affiliation-links)$$if(affiliation-url)$<a href="$affiliation-url$">$affiliation-name$</a>$else$$affiliation-name$$endif$$sep$ · $endfor$
</div> </div>
$endif$ $endif$
$if(status)$
<div class="meta-row meta-epistemic-strip" data-pagefind-ignore="all">
$if(overall-score)$<span class="ep-trust" title="Trust score: $overall-score$/100 (confidence × 0.6 + evidence × 0.4)"><span class="ep-score">$overall-score$%</span> trust</span>$endif$
<span class="ep-status">$status$</span>
$if(confidence)$<span class="ep-row" title="Confidence">$confidence$% confidence</span>$endif$
$if(importance-dots)$<span class="ep-row" title="Importance: $importance$/5"><span class="ep-dots">$importance-dots$</span> importance</span>$endif$
$if(evidence-dots)$<span class="ep-row" title="Evidence quality: $evidence$/5"><span class="ep-dots">$evidence-dots$</span> evidence quality</span>$endif$
$if(scope)$<span class="ep-row" title="Scope">$scope$ scope</span>$endif$
$if(novelty)$<span class="ep-row" title="Novelty">$novelty$ novelty</span>$endif$
$if(practicality)$<span class="ep-row" title="Practicality">$practicality$ practicality</span>$endif$
</div>
$endif$
<nav class="meta-row meta-pagelinks" aria-label="Page sections"> <nav class="meta-row meta-pagelinks" aria-label="Page sections">
<a href="#version-history">History</a> <a href="#version-history">History</a>
$if(status)$<a href="#epistemic">$if(overall-score)$<span class="ep-link-label">Epistemic</span><span class="meta-overall-score" title="Overall score: $overall-score$/100">$overall-score$%</span>$else$Epistemic$endif$</a>$endif$ $if(status)$<a href="#epistemic">Epistemic</a>$endif$
$if(bibliography)$<a href="#bibliography">Bibliography</a>$endif$ $if(bibliography)$<a href="#bibliography">Bibliography</a>$endif$
$if(backlinks)$<a href="#backlinks">Backlinks</a>$endif$ $if(backlinks)$<a href="#backlinks">Backlinks</a>$endif$
$if(repository)$<a href="$repository$">Repository</a>$endif$ $if(repository)$<a href="$repository$">Repository</a>$endif$

View File

@ -35,23 +35,11 @@
$if(status)$ $if(status)$
<div class="meta-footer-section meta-footer-epistemic" id="epistemic"> <div class="meta-footer-section meta-footer-epistemic" id="epistemic">
<h3><a href="/colophon.html#living-documents">Epistemic</a></h3> <h3><a href="/colophon.html#living-documents">Epistemic</a></h3>
<div class="ep-compact">
<span class="ep-status">$status$</span>
$if(confidence)$<span class="ep-confidence" title="Confidence">$confidence$%</span>$endif$
$if(importance-dots)$<span class="ep-dots" title="Importance: $importance$/5">$importance-dots$</span>$endif$
$if(evidence-dots)$<span class="ep-dots" title="Evidence quality: $evidence$/5">$evidence-dots$</span>$endif$
</div>
<details class="ep-details">
<summary class="ep-summary">detail</summary>
<dl class="ep-expanded"> <dl class="ep-expanded">
<dt>Stability</dt><dd>$stability$</dd> <dt>Stability</dt><dd>$stability$</dd>
$if(scope)$<dt>Scope</dt><dd>$scope$</dd>$endif$
$if(novelty)$<dt>Novelty</dt><dd>$novelty$</dd>$endif$
$if(practicality)$<dt>Practicality</dt><dd>$practicality$</dd>$endif$
$if(last-reviewed)$<dt>Last reviewed</dt><dd>$last-reviewed$</dd>$endif$ $if(last-reviewed)$<dt>Last reviewed</dt><dd>$last-reviewed$</dd>$endif$
$if(confidence-trend)$<dt>Confidence trend</dt><dd>$confidence-trend$</dd>$endif$ $if(confidence-trend)$<dt>Confidence trend</dt><dd>$confidence-trend$</dd>$endif$
</dl> </dl>
</details>
</div> </div>
$endif$ $endif$