@panaversity/ksor 0.0.29 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -0
- package/README.md +24 -0
- package/dist/cli.mjs +11 -3
- package/dist/{gateway-api-8lNruq9e-CuohjtoK.mjs → gateway-api-6nC9x54K-BWFTI_6U.mjs} +1 -1
- package/dist/gateway.mjs +1 -1
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +11 -1
- package/templates/scaffold/.agents/skills/make-slides/SKILL.md +160 -0
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +11 -1
- package/templates/scaffold/.claude/skills/make-slides/SKILL.md +160 -0
- package/templates/scaffold/AGENTS.md +91 -5
- package/templates/scaffold/README.md +24 -1
- package/templates/scaffold/knowledge/what-is-a-ksor.quiz.yaml +90 -0
- package/templates/scaffold/knowledge/what-is-a-ksor.slides.yaml +65 -0
- package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +17 -2
- package/templates/scaffold/system/site/app/global.css +113 -0
- package/templates/scaffold/system/site/components/deck-viewer.tsx +195 -0
- package/templates/scaffold/system/site/components/quiz.tsx +321 -0
- package/templates/scaffold/system/site/components/slides.tsx +128 -0
- package/templates/scaffold/system/site/components/study-aids.tsx +1 -1
- package/templates/scaffold/system/site/lib/attachment-rule.ts +14 -0
- package/templates/scaffold/system/site/lib/attachments.ts +88 -3
- package/templates/scaffold/system/site/lib/deck.ts +3 -12
- package/templates/scaffold/system/site/lib/identity.ts +55 -0
- package/templates/scaffold/system/site/lib/quiz-audit.ts +306 -0
- package/templates/scaffold/system/site/lib/quiz-round.ts +57 -0
- package/templates/scaffold/system/site/lib/quiz.ts +84 -0
- package/templates/scaffold/system/site/lib/slides-embed.ts +93 -0
- package/templates/scaffold/system/site/lib/slides.ts +123 -0
- package/templates/scaffold/system/site/source.config.ts +25 -0
|
@@ -506,11 +506,15 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
506
506
|
refused.
|
|
507
507
|
- Images and assets live in `knowledge/` beside the document that uses them,
|
|
508
508
|
referenced by relative links. A relative link must never leave `knowledge/`.
|
|
509
|
-
- **Study attachments.** A document may carry
|
|
510
|
-
after it, in the same folder: `<doc>.summary.md` (a short précis)
|
|
511
|
-
`<doc>.
|
|
512
|
-
|
|
513
|
-
document's
|
|
509
|
+
- **Study attachments.** A document may carry four optional companions named
|
|
510
|
+
after it, in the same folder: `<doc>.summary.md` (a short précis),
|
|
511
|
+
`<doc>.slides.yaml` (a presentation), `<doc>.flashcards.yaml` (a recall deck)
|
|
512
|
+
and `<doc>.quiz.yaml` (a multiple-choice check). The summary appears as a
|
|
513
|
+
second tab beside the document's own words; the presentation appears at the
|
|
514
|
+
TOP of the page, before the document, because a deck is the shape of the
|
|
515
|
+
thing and gives the detail somewhere to land; the deck and the quiz appear at
|
|
516
|
+
the END, because those are used after reading. None of them appears anywhere
|
|
517
|
+
else in the site.
|
|
514
518
|
|
|
515
519
|
An attachment is **part of its document**, not a document. It has no URL of
|
|
516
520
|
its own, no sidebar row, no line in `llms.txt`, and no identity an agent can
|
|
@@ -549,6 +553,86 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
549
553
|
kept in the reader's own browser, so it is per-person and per-device, and it
|
|
550
554
|
is not part of the record.
|
|
551
555
|
|
|
556
|
+
A **quiz** is the same idea with an answer to choose. Each question carries
|
|
557
|
+
two to six options, `answer` as the zero-based index of the correct one, a
|
|
558
|
+
required `explanation`, and an optional `source` naming where in the document
|
|
559
|
+
the answer lives:
|
|
560
|
+
|
|
561
|
+
```yaml
|
|
562
|
+
quiz:
|
|
563
|
+
title: Expense approvals
|
|
564
|
+
questionsPerRound: 10 # optional; the default
|
|
565
|
+
questions:
|
|
566
|
+
- question: Who approves a purchase above the threshold?
|
|
567
|
+
options:
|
|
568
|
+
- A second approver, independent of the requester
|
|
569
|
+
- The requester's own manager, in every case
|
|
570
|
+
- The finance team, after the purchase completes
|
|
571
|
+
answer: 0
|
|
572
|
+
explanation: Why that is right, and why the others are not.
|
|
573
|
+
source: Approvals — thresholds
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
The reader sees whether they were right **immediately**, then the
|
|
577
|
+
explanation — a quiz here teaches through the mistake, and it does not
|
|
578
|
+
certify anybody, so there is no pass mark. Answers stay in the reader's
|
|
579
|
+
browser and are sent nowhere. A bank larger than `questionsPerRound` is drawn
|
|
580
|
+
from at random and offers another round; a smaller one is simply asked in
|
|
581
|
+
full.
|
|
582
|
+
|
|
583
|
+
**`pnpm check` and `pnpm build` both refuse a quiz a reader could pass
|
|
584
|
+
without reading**, and this is worth knowing before you write one, because
|
|
585
|
+
it is easy to trip by accident:
|
|
586
|
+
|
|
587
|
+
| Refusal | Means |
|
|
588
|
+
| -------------------------- | ----------------------------------------------------- |
|
|
589
|
+
| `ksor-quiz-answer-bias` | more than 60% of answers sit at one option position |
|
|
590
|
+
| `ksor-quiz-length-bias` | picking the longest (or shortest) option usually wins |
|
|
591
|
+
| `ksor-quiz-answer-run` | four or more questions in a row share an answer |
|
|
592
|
+
| `ksor-quiz-contradiction` | an explanation calls the marked answer wrong |
|
|
593
|
+
| `ksor-quiz-duplicate-stem` | two questions open with the same 60 characters |
|
|
594
|
+
|
|
595
|
+
So: move the correct answer around, and keep the options close in length.
|
|
596
|
+
The ratio rules do not apply below five questions, where spreading answers
|
|
597
|
+
would mean the checker choosing them for you. These checks come from the
|
|
598
|
+
predecessor, where the same mistakes shipped and were found by readers — one
|
|
599
|
+
quiz had every correct answer in the same position across 451 questions.
|
|
600
|
+
|
|
601
|
+
A **presentation** is slides the record owns. Ask your coding agent for one
|
|
602
|
+
rather than writing it by hand — `make slides for knowledge/<path>.md` runs
|
|
603
|
+
the `make-slides` skill, which reads the document, writes the deck, checks
|
|
604
|
+
every claim back against it, and tells you what it left out:
|
|
605
|
+
|
|
606
|
+
```yaml
|
|
607
|
+
slides:
|
|
608
|
+
title: Expense approvals
|
|
609
|
+
description: The 15-minute version, for a room.
|
|
610
|
+
deck:
|
|
611
|
+
- heading: When two copies disagree, one wins
|
|
612
|
+
bullets:
|
|
613
|
+
- Two approvers above the threshold, always
|
|
614
|
+
- The threshold is per invoice, including tax
|
|
615
|
+
note: Spoken, never shown. Pause here — people remember this one wrong.
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
`heading` is required and should be a claim rather than a label; `lead` is
|
|
619
|
+
one sentence for a single-point slide; `bullets` caps at six because a slide
|
|
620
|
+
someone reads aloud is a slide nobody listens to; `note` is what the
|
|
621
|
+
presenter SAYS and never appears on the slide, including in fullscreen.
|
|
622
|
+
|
|
623
|
+
The slides render in the page — no third party, no request to anyone, and
|
|
624
|
+
every slide is in the shipped HTML, so a reader without JavaScript still gets
|
|
625
|
+
the whole deck. Because they live in the record they are reviewed in the same
|
|
626
|
+
pull request as the document, versioned with it, and withdrawn with it.
|
|
627
|
+
|
|
628
|
+
**A deck you keep elsewhere** can be pointed at instead — `slides.url:` and
|
|
629
|
+
no `deck:`. The embed url is derived for Google Slides, Canva and SlideShare;
|
|
630
|
+
anything else needs an explicit `embed:` or renders as a link. The url must
|
|
631
|
+
be `https` (a browser blocks a mixed-content frame silently), and the frame
|
|
632
|
+
loads on CLICK, so nothing is requested from the host until a reader asks.
|
|
633
|
+
Declaring both `deck:` and `slides.url:` is refused — two presentations with
|
|
634
|
+
nothing to say which one governs.
|
|
635
|
+
|
|
552
636
|
- Copy load-bearing values (numbers, thresholds, dates) exactly from their
|
|
553
637
|
source, and name the source in `provenance`.
|
|
554
638
|
|
|
@@ -571,6 +655,8 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
571
655
|
write `instance.md` together.
|
|
572
656
|
- `.agents/skills/add-sources/` — turn source material (documents, pages,
|
|
573
657
|
notes) into governed knowledge.
|
|
658
|
+
- `.agents/skills/make-slides/` — generate a presentation from one document
|
|
659
|
+
and attach it, so it renders on that document's page.
|
|
574
660
|
- `.agents/skills/format-checker/` — the rules above, as a program;
|
|
575
661
|
`pnpm check` runs it and its errors explain how to fix themselves.
|
|
576
662
|
|
|
@@ -23,6 +23,29 @@ versions that bundle corepack. The first `pnpm install` also fetches the
|
|
|
23
23
|
`ksor` tool (pinned in `package.json`) and writes it into your lockfile —
|
|
24
24
|
commit the updated lockfile.
|
|
25
25
|
|
|
26
|
+
### Presenting a document
|
|
27
|
+
|
|
28
|
+
Ask your coding agent for slides and it writes them, from the document, into
|
|
29
|
+
the record:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
make slides for knowledge/expenses/approvals.md
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The `make-slides` skill reads the document whole, writes the deck into
|
|
36
|
+
`knowledge/expenses/approvals.slides.yaml`, checks every claim and every
|
|
37
|
+
number back against the document, and tells you what it left out because the
|
|
38
|
+
document did not support it — which is usually how you find out a document has
|
|
39
|
+
a gap. The deck then renders at the top of that document's page: click through
|
|
40
|
+
it inline, or **Present** for fullscreen. Presenter notes stay off the screen.
|
|
41
|
+
|
|
42
|
+
The slides live in the record, so they are reviewed in the same pull request
|
|
43
|
+
as the document, versioned with it, and withdrawn when it is withdrawn. There
|
|
44
|
+
is no third party and no link to rot. If you already keep a deck in Google
|
|
45
|
+
Slides, Canva or SlideShare you can point at it instead — `slides.url:` rather
|
|
46
|
+
than `deck:` — and the page will offer it as a link with a frame the reader
|
|
47
|
+
loads on click, so nothing is requested from the host until somebody asks.
|
|
48
|
+
|
|
26
49
|
### Serving to agents
|
|
27
50
|
|
|
28
51
|
The record's other surface is an MCP server for AI agents — the same
|
|
@@ -128,7 +151,7 @@ different coding agent's way of finding the same working contract.
|
|
|
128
151
|
| `instance.md` | what this record is authoritative for; its `name:` is the identity every surface publishes (read at server/build start — restart `pnpm dev` after renaming). This prose IS the agent surface's system prompt — `ksor serve` wires it into the MCP server's instructions. |
|
|
129
152
|
| `AGENTS.md` | the working contract every coding agent reads first — the rules for writing knowledge here. |
|
|
130
153
|
| `CLAUDE.md` | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one. |
|
|
131
|
-
| `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `format-checker` (the rules, as a program). |
|
|
154
|
+
| `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `make-slides` (generate a presentation from a document and attach it), `format-checker` (the rules, as a program). |
|
|
132
155
|
| `.claude/skills/` | byte-identical copies of the kit — Claude Code discovers skills only here. The checker enforces the mirror, so the two cannot drift. |
|
|
133
156
|
| `.gemini/settings.json` | points Gemini CLI at `AGENTS.md`; Gemini does not read that filename on its own. |
|
|
134
157
|
| `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# A quiz for what-is-a-ksor.md.
|
|
2
|
+
#
|
|
3
|
+
# Every question and every answer states only what its parent document states —
|
|
4
|
+
# a quiz is a way of checking the record, never a second source. Ask your coding
|
|
5
|
+
# agent to write one from a document, and to check each answer back against it.
|
|
6
|
+
#
|
|
7
|
+
# Two habits worth copying. The options are kept close in LENGTH, because if the
|
|
8
|
+
# correct answer is reliably the longest one a reader passes by looking rather
|
|
9
|
+
# than by reading. And the correct answer moves POSITION between questions: a
|
|
10
|
+
# quiz whose answer is usually B is a quiz you can pass without the document.
|
|
11
|
+
# `pnpm check` and `pnpm build` both refuse a quiz where either is true — the
|
|
12
|
+
# first draft of this one was refused for putting four of five answers at B.
|
|
13
|
+
quiz:
|
|
14
|
+
title: Check yourself
|
|
15
|
+
description: Five questions on what this record is, and what it is not.
|
|
16
|
+
questions:
|
|
17
|
+
- question: A spreadsheet and the ledger disagree about a number. Which one is authoritative?
|
|
18
|
+
options:
|
|
19
|
+
- Whichever of the two was most recently edited
|
|
20
|
+
- The spreadsheet, being closer to the daily work
|
|
21
|
+
- The ledger, because it is the governing copy
|
|
22
|
+
- Neither, until a person reconciles the two
|
|
23
|
+
answer: 2
|
|
24
|
+
explanation: >
|
|
25
|
+
A system of record is the copy that governs, so the ledger wins by
|
|
26
|
+
definition rather than by being newer or closer to the work. Recency is
|
|
27
|
+
not authority: an edit made this morning in a spreadsheet nobody governs
|
|
28
|
+
is still ungoverned. Reconciliation is something a record makes possible,
|
|
29
|
+
not a precondition for having an answer at all.
|
|
30
|
+
source: The opening definition
|
|
31
|
+
|
|
32
|
+
- question: What does a KSoR settle that a traditional system of record does not?
|
|
33
|
+
options:
|
|
34
|
+
- The state of a business, such as its balances
|
|
35
|
+
- Which vendor an organization has chosen to use
|
|
36
|
+
- How much storage the organization is paying for
|
|
37
|
+
- What the organization knows and how it operates
|
|
38
|
+
answer: 3
|
|
39
|
+
explanation: >
|
|
40
|
+
A traditional system of record settles the state of a business — what is
|
|
41
|
+
owed, what is owned, what was transacted. A KSoR settles the layer above
|
|
42
|
+
it: which policies apply, which thresholds are approved, and what a term
|
|
43
|
+
means inside this organization. Vendor choice and storage cost are
|
|
44
|
+
consequences of running one, never what it is authoritative for.
|
|
45
|
+
source: What this record settles
|
|
46
|
+
|
|
47
|
+
- question: Why can an ordinary assistant not tell you which of its sentences were checked?
|
|
48
|
+
options:
|
|
49
|
+
- It answers from everything it has ever read
|
|
50
|
+
- It was not given enough context to work with
|
|
51
|
+
- Its answers are checked, but only in summary
|
|
52
|
+
- It lacks permission to reveal its own sources
|
|
53
|
+
answer: 0
|
|
54
|
+
explanation: >
|
|
55
|
+
It answers from everything it has ever read, and nothing in that process
|
|
56
|
+
separates a checked claim from an unchecked one. This is not a context
|
|
57
|
+
problem and not a permissions problem: more context cannot create a
|
|
58
|
+
distinction that was never recorded in the first place. The record closes
|
|
59
|
+
the gap by governing what gets read.
|
|
60
|
+
source: Why an assistant cannot answer that question
|
|
61
|
+
|
|
62
|
+
- question: What problem does a KSoR exist to solve?
|
|
63
|
+
options:
|
|
64
|
+
- Storage costs across a growing organization
|
|
65
|
+
- Scatter, with no authoritative copy to trust
|
|
66
|
+
- The speed at which an assistant can reply
|
|
67
|
+
- The difficulty of writing documentation well
|
|
68
|
+
answer: 1
|
|
69
|
+
explanation: >
|
|
70
|
+
The problem is scatter: knowledge spread across wikis, decks, PDFs,
|
|
71
|
+
prompts and somebody's memory, with no authoritative answer to which of
|
|
72
|
+
them an agent should trust. Speed and cost are not what the record
|
|
73
|
+
addresses, and writing well is valuable but does not by itself tell an
|
|
74
|
+
agent which of two documents governs.
|
|
75
|
+
source: The problem it solves
|
|
76
|
+
|
|
77
|
+
- question: Abstention — answering "not in this corpus" — is best described as what?
|
|
78
|
+
options:
|
|
79
|
+
- An error state the record should minimise
|
|
80
|
+
- A temporary gap, pending the next ingest
|
|
81
|
+
- A setting an operator turns on when ready
|
|
82
|
+
- A correct answer the record is meant to give
|
|
83
|
+
answer: 3
|
|
84
|
+
explanation: >
|
|
85
|
+
Abstention is a correct answer, never an error and never a licence to
|
|
86
|
+
fall back on general model knowledge. A record that answers everything
|
|
87
|
+
has stopped being a record of anything in particular. It is not a gap
|
|
88
|
+
waiting to be filled, and while the gate that enforces it is configured
|
|
89
|
+
deliberately, the honesty it expresses is the product itself.
|
|
90
|
+
source: What this record settles, and what it does not
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# The presentation that teaches what-is-a-ksor.md.
|
|
2
|
+
#
|
|
3
|
+
# These slides live IN the record: reviewed in a pull request, versioned with
|
|
4
|
+
# the document, withdrawn when it is withdrawn, and rendered by the site — so
|
|
5
|
+
# there is no third party, no dead link, and nothing to keep in step by hand.
|
|
6
|
+
# Your coding agent writes them from the document; `.agents/skills/make-slides/`
|
|
7
|
+
# is the procedure.
|
|
8
|
+
#
|
|
9
|
+
# A slide may only say what the document says. A deck is a way of presenting
|
|
10
|
+
# the record, never a second source.
|
|
11
|
+
slides:
|
|
12
|
+
title: Introducing the record
|
|
13
|
+
description: The 15-minute version, for a room.
|
|
14
|
+
deck:
|
|
15
|
+
# The opening slide does NOT repeat the page title. It sits directly under
|
|
16
|
+
# it, so restating it wastes the one slide everyone actually looks at.
|
|
17
|
+
- heading: Which copy should an agent trust?
|
|
18
|
+
lead: That question is what this record exists to answer, and nothing in an ordinary assistant can.
|
|
19
|
+
note: Open with the question, not the definition. The definition lands after they feel the problem.
|
|
20
|
+
|
|
21
|
+
- heading: The problem it solves
|
|
22
|
+
bullets:
|
|
23
|
+
- Knowledge is scattered across wikis, decks, PDFs, prompts and memory
|
|
24
|
+
- No authoritative answer to the question an agent has to ask
|
|
25
|
+
- Which of these copies should I trust?
|
|
26
|
+
note: Ask the room where their real answer lives today. Wait for the disagreement — it always comes.
|
|
27
|
+
|
|
28
|
+
- heading: When two copies disagree, one wins
|
|
29
|
+
bullets:
|
|
30
|
+
- A traditional system of record settles the state of a business
|
|
31
|
+
- The ledger is authoritative; the spreadsheet is not
|
|
32
|
+
- Recency is not authority — a fresh edit to an ungoverned copy is still ungoverned
|
|
33
|
+
note: This is the whole idea. If they take one slide away, it is this one.
|
|
34
|
+
|
|
35
|
+
- heading: AI never had one
|
|
36
|
+
bullets:
|
|
37
|
+
- An assistant answers from everything it has ever read
|
|
38
|
+
- Nothing in that separates a checked claim from an unchecked one
|
|
39
|
+
- Which is exactly why it cannot tell you which of its sentences were verified
|
|
40
|
+
note: Not a criticism of the models. It is a missing layer, and that layer is what we are building.
|
|
41
|
+
|
|
42
|
+
- heading: What this record settles
|
|
43
|
+
bullets:
|
|
44
|
+
- Which policies apply, and which thresholds are approved
|
|
45
|
+
- What a term means inside this organization
|
|
46
|
+
- What to do when the answer is not known
|
|
47
|
+
note: Point at the third one. It is the one people do not expect.
|
|
48
|
+
|
|
49
|
+
- heading: Abstention is a feature
|
|
50
|
+
lead: '"Not in this corpus" is a correct answer — never an error, and never a licence to fall back on model knowledge.'
|
|
51
|
+
note: A record that answers everything has stopped being a record of anything in particular.
|
|
52
|
+
|
|
53
|
+
- heading: One source, two surfaces
|
|
54
|
+
bullets:
|
|
55
|
+
- The website is for people
|
|
56
|
+
- The MCP server is for agents
|
|
57
|
+
- Both render the same build — they can never disagree
|
|
58
|
+
note: Open llms.txt beside the page here. Seeing one source render twice lands better than describing it.
|
|
59
|
+
|
|
60
|
+
- heading: Where the edges are
|
|
61
|
+
bullets:
|
|
62
|
+
- Provenance proves who said what, and when
|
|
63
|
+
- It does not prove the source was right — that is a separate judgment
|
|
64
|
+
- Governance is a ladder, not a gate; level 0 works on day one
|
|
65
|
+
note: Say the second bullet out loud. Overselling provenance is how these systems lose trust.
|
|
@@ -17,8 +17,10 @@ import { showGovernance } from "@/lib/shared";
|
|
|
17
17
|
import { RecordToc, TocItems } from "@/components/record-toc";
|
|
18
18
|
import { RecordViews } from "@/components/record-views";
|
|
19
19
|
import { Flashcards } from "@/components/flashcards";
|
|
20
|
+
import { Quiz } from "@/components/quiz";
|
|
21
|
+
import { Slides } from "@/components/slides";
|
|
20
22
|
import { StudyAids } from "@/components/study-aids";
|
|
21
|
-
import { deckFor, summaryFor } from "@/lib/attachments";
|
|
23
|
+
import { deckFor, quizFor, slidesFor, summaryFor } from "@/lib/attachments";
|
|
22
24
|
import { readingMinutes } from "@/lib/reading-time";
|
|
23
25
|
|
|
24
26
|
export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
@@ -32,6 +34,8 @@ export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
|
32
34
|
const summary = summaryFor(page.path);
|
|
33
35
|
const Summary = summary?.body ?? null;
|
|
34
36
|
const deck = deckFor(page.path);
|
|
37
|
+
const quiz = quizFor(page.path);
|
|
38
|
+
const presentation = slidesFor(page.path);
|
|
35
39
|
// Counted at BUILD time from the document's own markdown, so the figure is in
|
|
36
40
|
// the shipped HTML for a reader with a failed bundle, a crawler and an agent
|
|
37
41
|
// alike. The predecessor measured the rendered DOM after paint, which put it
|
|
@@ -122,6 +126,12 @@ export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
|
122
126
|
{showGovernance ? (
|
|
123
127
|
<GovernanceMeta governance={governance} replaces={replaces} markdownUrl={markdownUrl} />
|
|
124
128
|
) : null}
|
|
129
|
+
{/* BEFORE the document, not after it. The deck is the shape of the
|
|
130
|
+
thing — five minutes of slides gives the detail somewhere to land —
|
|
131
|
+
so it belongs where a reader meets it first, which is also where the
|
|
132
|
+
predecessor puts its own. The recall aids stay at the end, because
|
|
133
|
+
those are used AFTER reading. */}
|
|
134
|
+
{presentation === null ? null : <Slides slides={presentation} />}
|
|
125
135
|
{/* grow-0, against the shell's own `flex-1`: the article is a flex column
|
|
126
136
|
stretched to the viewport, so the body inflated from ~150px of text to
|
|
127
137
|
402px and pushed Sources and everything after it to the bottom of the
|
|
@@ -157,7 +167,12 @@ export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
|
|
|
157
167
|
region, so the quiz that will sit beside the deck is a child here and
|
|
158
168
|
not a new argument about where it goes. Renders nothing at all when
|
|
159
169
|
the document has no study aids. */}
|
|
160
|
-
|
|
170
|
+
{/* Recall first, then the check on it — and each renders only if the
|
|
171
|
+
document carries one, so a page with just a quiz shows just a quiz. */}
|
|
172
|
+
<StudyAids>
|
|
173
|
+
{deck === null ? null : <Flashcards deck={deck} />}
|
|
174
|
+
{quiz === null ? null : <Quiz quiz={quiz} />}
|
|
175
|
+
</StudyAids>
|
|
161
176
|
{/* A folder's index page lists what the folder holds. Without it the
|
|
162
177
|
page ended at its own sentence and the documents below it were
|
|
163
178
|
reachable only from the sidebar (research/site-design.md F5). Empty
|
|
@@ -274,6 +274,119 @@ html > body[data-scroll-locked] {
|
|
|
274
274
|
--ksor-caution: #e0906e;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/* The slide STAGE.
|
|
278
|
+
|
|
279
|
+
A slide is a projected surface, and reading as one at a glance is most of
|
|
280
|
+
what makes a deck legible on a page full of prose. The first version painted
|
|
281
|
+
it `--muted` and it read as an empty placeholder rather than as a slide —
|
|
282
|
+
pale grey on a pale page, with nothing saying "this is a different medium".
|
|
283
|
+
|
|
284
|
+
So the stage is dark in BOTH themes, deliberately. That is not a palette
|
|
285
|
+
inconsistency: the page is a document and the stage is a projection, and the
|
|
286
|
+
whole job of this block is to look like the second thing while sitting
|
|
287
|
+
inside the first. Its ink is warm-neutral rather than pure white, which is
|
|
288
|
+
what stops a dark panel from glaring on a light page.
|
|
289
|
+
|
|
290
|
+
Light and dark differ only in how far the stage sits from its surround. */
|
|
291
|
+
:root {
|
|
292
|
+
--ksor-stage: oklch(0.21 0.018 258);
|
|
293
|
+
--ksor-stage-ink: oklch(0.96 0.005 90);
|
|
294
|
+
--ksor-stage-dim: oklch(0.74 0.012 258);
|
|
295
|
+
--ksor-stage-rule: oklch(0.32 0.02 258);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* In dark the stage must RISE off the page, not sink into it. A stage darker
|
|
299
|
+
than its surround measured L 4.4 against the page's 3.3 — near-identical,
|
|
300
|
+
so the slide lost its edges entirely and read as text floating on the page
|
|
301
|
+
(found live). On a dark ground, elevation reads as lighter. */
|
|
302
|
+
.dark {
|
|
303
|
+
--ksor-stage: oklch(0.26 0.019 258);
|
|
304
|
+
--ksor-stage-ink: oklch(0.96 0.005 90);
|
|
305
|
+
--ksor-stage-dim: oklch(0.76 0.012 258);
|
|
306
|
+
--ksor-stage-rule: oklch(0.38 0.02 258);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.ksor-stage {
|
|
310
|
+
background-color: var(--ksor-stage);
|
|
311
|
+
color: var(--ksor-stage-ink);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.ksor-stage-dim {
|
|
315
|
+
color: var(--ksor-stage-dim);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.ksor-stage-rule {
|
|
319
|
+
background-color: var(--ksor-stage-rule);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Right and wrong, for the quiz — the ONE place this record spends green and
|
|
323
|
+
red, and it earns them: a reader checking an answer is asking a true/false
|
|
324
|
+
question, and the accent cannot say "this is correct" while also saying
|
|
325
|
+
"this is what you picked". Both are needed at once when you answer wrongly.
|
|
326
|
+
|
|
327
|
+
Colour is never the only channel: the correct option also carries a check,
|
|
328
|
+
the wrong one a cross, and the line above states the verdict in words — so
|
|
329
|
+
the pair works for a reader who cannot distinguish them. Declared as
|
|
330
|
+
light/dark pairs on the root for the reason `--ksor-caution` above records.
|
|
331
|
+
|
|
332
|
+
Deliberately NOT `--ksor-caution`, which means "the record withdrew this".
|
|
333
|
+
A wrong answer is not a governance state. */
|
|
334
|
+
:root {
|
|
335
|
+
--ksor-correct: #1a7f4b;
|
|
336
|
+
--ksor-incorrect: #b3261e;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.dark {
|
|
340
|
+
--ksor-correct: #6edaa0;
|
|
341
|
+
--ksor-incorrect: #f2897f;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* The three states an answered question shows at once. Written as CLASSES,
|
|
345
|
+
the way `.ksor-caution` below is, rather than as Tailwind arbitrary values:
|
|
346
|
+
`border-[color:var(--x)]` emits a rule whose `var()` did not resolve on the
|
|
347
|
+
element in a real build (found live 2026-08-23 — the class was present, the
|
|
348
|
+
rule was in the stylesheet, the token computed correctly on the element, and
|
|
349
|
+
the border still painted the default hairline). A plain class has none of
|
|
350
|
+
that ambiguity, and this file is already where this record's semantic colour
|
|
351
|
+
lives. */
|
|
352
|
+
.ksor-answer-correct {
|
|
353
|
+
border-color: var(--ksor-correct);
|
|
354
|
+
background-color: color-mix(in oklab, var(--ksor-correct) 10%, transparent);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.ksor-answer-wrong {
|
|
358
|
+
border-color: var(--ksor-incorrect);
|
|
359
|
+
background-color: color-mix(in oklab, var(--ksor-incorrect) 10%, transparent);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* "This is the one you picked" — kept in the accent, and kept SEPARATE from
|
|
363
|
+
right/wrong, because after a wrong answer a reader needs all three facts. */
|
|
364
|
+
.ksor-answer-mine {
|
|
365
|
+
outline: 2px solid var(--color-fd-primary);
|
|
366
|
+
outline-offset: 1px;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.ksor-answer-correct-text {
|
|
370
|
+
color: var(--ksor-correct);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.ksor-answer-wrong-text {
|
|
374
|
+
color: var(--ksor-incorrect);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.dark .ksor-answer-correct,
|
|
378
|
+
.dark .ksor-answer-wrong {
|
|
379
|
+
background-color: color-mix(in oklab, currentColor 0%, transparent);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.dark .ksor-answer-correct {
|
|
383
|
+
background-color: color-mix(in oklab, var(--ksor-correct) 14%, transparent);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.dark .ksor-answer-wrong {
|
|
387
|
+
background-color: color-mix(in oklab, var(--ksor-incorrect) 14%, transparent);
|
|
388
|
+
}
|
|
389
|
+
|
|
277
390
|
.ksor-caution {
|
|
278
391
|
border-color: color-mix(in oklab, var(--ksor-caution) 32%, transparent);
|
|
279
392
|
border-left-color: var(--ksor-caution);
|