@praxisflux/gates 0.5.0
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/LICENSE +21 -0
- package/README.md +74 -0
- package/codebase-to-course/gates/cli.mjs +20 -0
- package/codebase-to-course/gates/course.mjs +58 -0
- package/codebase-to-course/lib/README.md +8 -0
- package/codebase-to-course/lib/cli.mjs +23 -0
- package/codebase-to-course/lib/dates.mjs +11 -0
- package/codebase-to-course/lib/gate-runner.mjs +69 -0
- package/codebase-to-course/lib/handoff.mjs +75 -0
- package/codebase-to-course/lib/html/base.html +129 -0
- package/codebase-to-course/lib/installer.mjs +52 -0
- package/codebase-to-course/lib/lifecycle.mjs +67 -0
- package/codebase-to-course/lib/markdown.mjs +81 -0
- package/codebase-to-course/lib/project-root.mjs +52 -0
- package/codebase-to-course/lib/selfcontained.mjs +37 -0
- package/codebase-to-course/lib/spec-derive.mjs +114 -0
- package/codebase-to-course/lib/template.mjs +11 -0
- package/codebase-to-course/lib/toolkit/README.md +45 -0
- package/codebase-to-course/lib/toolkit/code-translation.md +50 -0
- package/codebase-to-course/lib/toolkit/diagrams.md +57 -0
- package/codebase-to-course/lib/toolkit/pedagogy.md +39 -0
- package/codebase-to-course/lib/toolkit/quiz-patterns.md +65 -0
- package/codebase-to-course/lib/toolkit/svg-diagrams.md +59 -0
- package/codebase-to-course/lib/toolkit/tooltip.md +83 -0
- package/codebase-to-course/skills/codebase-to-course/references/validate.mjs +301 -0
- package/grounding-wiki/gates/cli.mjs +26 -0
- package/grounding-wiki/gates/freshness.mjs +92 -0
- package/grounding-wiki/lib/README.md +8 -0
- package/grounding-wiki/lib/cli.mjs +23 -0
- package/grounding-wiki/lib/dates.mjs +11 -0
- package/grounding-wiki/lib/gate-runner.mjs +69 -0
- package/grounding-wiki/lib/handoff.mjs +75 -0
- package/grounding-wiki/lib/html/base.html +129 -0
- package/grounding-wiki/lib/installer.mjs +52 -0
- package/grounding-wiki/lib/lifecycle.mjs +67 -0
- package/grounding-wiki/lib/markdown.mjs +81 -0
- package/grounding-wiki/lib/project-root.mjs +52 -0
- package/grounding-wiki/lib/selfcontained.mjs +37 -0
- package/grounding-wiki/lib/spec-derive.mjs +114 -0
- package/grounding-wiki/lib/template.mjs +11 -0
- package/grounding-wiki/lib/toolkit/README.md +45 -0
- package/grounding-wiki/lib/toolkit/code-translation.md +50 -0
- package/grounding-wiki/lib/toolkit/diagrams.md +57 -0
- package/grounding-wiki/lib/toolkit/pedagogy.md +39 -0
- package/grounding-wiki/lib/toolkit/quiz-patterns.md +65 -0
- package/grounding-wiki/lib/toolkit/svg-diagrams.md +59 -0
- package/grounding-wiki/lib/toolkit/tooltip.md +83 -0
- package/lib/README.md +8 -0
- package/lib/cli.mjs +23 -0
- package/lib/dates.mjs +11 -0
- package/lib/gate-runner.mjs +69 -0
- package/lib/handoff.mjs +75 -0
- package/lib/html/base.html +129 -0
- package/lib/installer.mjs +52 -0
- package/lib/lifecycle.mjs +67 -0
- package/lib/markdown.mjs +81 -0
- package/lib/project-root.mjs +52 -0
- package/lib/selfcontained.mjs +37 -0
- package/lib/spec-derive.mjs +114 -0
- package/lib/template.mjs +11 -0
- package/lib/toolkit/README.md +45 -0
- package/lib/toolkit/code-translation.md +50 -0
- package/lib/toolkit/diagrams.md +57 -0
- package/lib/toolkit/pedagogy.md +39 -0
- package/lib/toolkit/quiz-patterns.md +65 -0
- package/lib/toolkit/svg-diagrams.md +59 -0
- package/lib/toolkit/tooltip.md +83 -0
- package/package.json +17 -0
- package/scripts/run-gates.mjs +91 -0
- package/spec-bridge/gates/bridge.mjs +130 -0
- package/spec-bridge/gates/cli.mjs +37 -0
- package/spec-bridge/lib/README.md +8 -0
- package/spec-bridge/lib/cli.mjs +23 -0
- package/spec-bridge/lib/dates.mjs +11 -0
- package/spec-bridge/lib/gate-runner.mjs +69 -0
- package/spec-bridge/lib/handoff.mjs +75 -0
- package/spec-bridge/lib/html/base.html +129 -0
- package/spec-bridge/lib/installer.mjs +52 -0
- package/spec-bridge/lib/lifecycle.mjs +67 -0
- package/spec-bridge/lib/markdown.mjs +81 -0
- package/spec-bridge/lib/project-root.mjs +52 -0
- package/spec-bridge/lib/selfcontained.mjs +37 -0
- package/spec-bridge/lib/spec-derive.mjs +114 -0
- package/spec-bridge/lib/template.mjs +11 -0
- package/spec-bridge/lib/toolkit/README.md +45 -0
- package/spec-bridge/lib/toolkit/code-translation.md +50 -0
- package/spec-bridge/lib/toolkit/diagrams.md +57 -0
- package/spec-bridge/lib/toolkit/pedagogy.md +39 -0
- package/spec-bridge/lib/toolkit/quiz-patterns.md +65 -0
- package/spec-bridge/lib/toolkit/svg-diagrams.md +59 -0
- package/spec-bridge/lib/toolkit/tooltip.md +83 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// spec-derive.mjs — derive a kanban status from a Spec Kit spec directory.
|
|
2
|
+
//
|
|
3
|
+
// The spec-bridge contract: the spec dir (specs/NNN-feature/) is the source of truth and this
|
|
4
|
+
// module is the ONLY place its lifecycle is interpreted. It is a pure, stateless read — every
|
|
5
|
+
// call re-reads spec.md / plan.md / tasks.md and re-derives, so a regenerated tasks.md
|
|
6
|
+
// (renamed, added, or removed phases) is a non-event: nothing is cached, no residue survives.
|
|
7
|
+
//
|
|
8
|
+
// Status rules (gaps between the canonical Spec Kit stages resolve to the nearer honest state):
|
|
9
|
+
// - no spec.md -> "To Do"
|
|
10
|
+
// - spec.md present, not yet Done-eligible -> "In Progress"
|
|
11
|
+
// - plan.md present AND tasks.md has >=1 task, all
|
|
12
|
+
// checked -> "Done-eligible"
|
|
13
|
+
// "Done-eligible" deliberately isn't "Done": the sync skill may move the Backlog task to Done,
|
|
14
|
+
// and the bridge gate treats a Done status without this derivation as a blocking problem.
|
|
15
|
+
//
|
|
16
|
+
// Strict mode ({ requireAnalysis: true }): checked boxes are necessary but weak proof, so
|
|
17
|
+
// Done-eligible additionally requires an analysis report saved as analysis.md in the spec dir
|
|
18
|
+
// (the durable artifact of /speckit.analyze — chat output doesn't count) with no unresolved
|
|
19
|
+
// CRITICAL findings. The scan is line-based: a line containing the word CRITICAL counts as an
|
|
20
|
+
// unresolved finding unless the same line says "resolved" (or carries a checked box).
|
|
21
|
+
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
|
|
25
|
+
export const STATUS = {
|
|
26
|
+
TODO: "To Do",
|
|
27
|
+
IN_PROGRESS: "In Progress",
|
|
28
|
+
DONE_ELIGIBLE: "Done-eligible",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const PHASE_HEADING = /^##\s+(.+?)\s*$/;
|
|
32
|
+
const TASK_LINE = /^\s*[-*]\s+\[([ xX])\]\s+\S/;
|
|
33
|
+
|
|
34
|
+
/** Strip Spec Kit's "Phase 3.1:" style prefix so phase names read as AC labels ("Setup"). */
|
|
35
|
+
function phaseName(heading) {
|
|
36
|
+
return heading.replace(/^phase\s+[\d.]+\s*:\s*/i, "").trim() || heading;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pure parser: tasks.md markdown -> [{ name, done, total }] in document order.
|
|
41
|
+
* A phase is a `##` heading; a task is any checkbox list line under it. Checkbox lines before
|
|
42
|
+
* the first heading are collected under a synthetic "Tasks" phase. Phases with no checkbox
|
|
43
|
+
* lines (e.g. a "Dependencies" notes section) are dropped.
|
|
44
|
+
*/
|
|
45
|
+
export function parseTasks(markdown) {
|
|
46
|
+
const phases = [];
|
|
47
|
+
let current = null;
|
|
48
|
+
for (const line of String(markdown ?? "").split("\n")) {
|
|
49
|
+
const heading = line.match(PHASE_HEADING);
|
|
50
|
+
if (heading) {
|
|
51
|
+
current = { name: phaseName(heading[1]), done: 0, total: 0 };
|
|
52
|
+
phases.push(current);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const task = line.match(TASK_LINE);
|
|
56
|
+
if (task) {
|
|
57
|
+
if (!current) {
|
|
58
|
+
current = { name: "Tasks", done: 0, total: 0 };
|
|
59
|
+
phases.push(current);
|
|
60
|
+
}
|
|
61
|
+
current.total += 1;
|
|
62
|
+
if (task[1] !== " ") current.done += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return phases.filter((p) => p.total > 0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** "Setup: 2/2 · Core: 4/7" — one segment per phase, empty string when there are no tasks. */
|
|
69
|
+
export function progressNote(phases) {
|
|
70
|
+
return phases.map((p) => `${p.name}: ${p.done}/${p.total}`).join(" · ");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Unresolved CRITICAL findings in an analysis report, line-based: any line containing the
|
|
75
|
+
* word CRITICAL that isn't marked handled on that same line ("resolved" or a checked box).
|
|
76
|
+
*/
|
|
77
|
+
export function findCriticalFindings(markdown) {
|
|
78
|
+
return String(markdown ?? "")
|
|
79
|
+
.split("\n")
|
|
80
|
+
.filter((l) => /\bCRITICAL\b/.test(l) && !/resolved/i.test(l) && !/\[[xX]\]/.test(l))
|
|
81
|
+
.map((l) => l.trim());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Derive the bridge state for one spec dir. Never throws: unreadable or malformed files
|
|
86
|
+
* degrade to "that artifact isn't there yet", so a broken dir derives as an early stage
|
|
87
|
+
* rather than crashing a sync or a Stop hook.
|
|
88
|
+
*/
|
|
89
|
+
export function deriveSpecState(specDir, { requireAnalysis = false } = {}) {
|
|
90
|
+
const has = (name) => existsSync(join(specDir, name));
|
|
91
|
+
const read = (name) => {
|
|
92
|
+
try { return has(name) ? readFileSync(join(specDir, name), "utf8") : ""; } catch { return ""; }
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const phases = parseTasks(read("tasks.md"));
|
|
96
|
+
const tasksTotal = phases.reduce((n, p) => n + p.total, 0);
|
|
97
|
+
const tasksDone = phases.reduce((n, p) => n + p.done, 0);
|
|
98
|
+
|
|
99
|
+
const analysisPresent = has("analysis.md");
|
|
100
|
+
const criticals = requireAnalysis && analysisPresent ? findCriticalFindings(read("analysis.md")) : [];
|
|
101
|
+
const analysisOk = !requireAnalysis || (analysisPresent && criticals.length === 0);
|
|
102
|
+
|
|
103
|
+
let status = STATUS.TODO;
|
|
104
|
+
if (has("spec.md")) {
|
|
105
|
+
status = has("plan.md") && tasksTotal > 0 && tasksDone === tasksTotal && analysisOk
|
|
106
|
+
? STATUS.DONE_ELIGIBLE
|
|
107
|
+
: STATUS.IN_PROGRESS;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
status, phases, tasksDone, tasksTotal, progressNote: progressNote(phases),
|
|
112
|
+
analysis: { required: requireAnalysis, present: analysisPresent, criticals },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// template.mjs — the tiny {{PLACEHOLDER}} substitution used when planting boilerplate.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Replace `{{NAME}}` tokens in `text` with `vars[NAME]`. Unknown tokens are left
|
|
5
|
+
* verbatim so a template can be rendered in passes. Names are `[A-Z0-9_]+`.
|
|
6
|
+
*/
|
|
7
|
+
export function render(text, vars = {}) {
|
|
8
|
+
return (text || "").replace(/\{\{\s*([A-Z0-9_]+)\s*\}\}/g, (whole, key) =>
|
|
9
|
+
Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : whole,
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# lib/toolkit — shared educational content modules
|
|
2
|
+
|
|
3
|
+
Reusable teaching and visual tools shared across the praxis plugins. Where the rest of `lib/`
|
|
4
|
+
is shared *plumbing* (code the gates and scripts import), `toolkit/` is shared *content*:
|
|
5
|
+
authoring guidance and copy-paste CSS/JS snippets a skill reads while producing a deck, course,
|
|
6
|
+
or briefing. The full convention lives in
|
|
7
|
+
[`docs/skill-patterns.md`](../../docs/skill-patterns.md) ("Shared content modules — the toolkit").
|
|
8
|
+
|
|
9
|
+
The convention in brief:
|
|
10
|
+
|
|
11
|
+
- **Vendored, not linked.** `scripts/build.mjs` copies `lib/` wholesale into every packaged
|
|
12
|
+
plugin, so each installed plugin carries its own copy — no runtime cross-plugin dependency.
|
|
13
|
+
- **Referenced as `${CLAUDE_PLUGIN_ROOT}/lib/toolkit/<module>.md`** from skill prose and
|
|
14
|
+
templates. Gate code doesn't import from toolkit/ (it's content, not plumbing).
|
|
15
|
+
- **Optional, always.** A skill that uses a module must state a one-line inline fallback and
|
|
16
|
+
still function when the module is absent (graceful degradation).
|
|
17
|
+
- **Styled by the shared token schema.** Snippets use the shared CSS custom-property *names*
|
|
18
|
+
(see `lib/html/base.html`) so they inherit whichever palette the consuming plugin defines.
|
|
19
|
+
|
|
20
|
+
## Modules
|
|
21
|
+
|
|
22
|
+
| Module | What it provides | Status |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `tooltip.md` | Jargon-glossary hover/tap tooltip (overflow-safe, mouse + touch; stamped into consumers by `scripts/sync-shared.mjs`) | shipped |
|
|
25
|
+
| `pedagogy.md` | Visual-teaching principles: one idea per screen, show don't tell, lead with the point, split don't shrink, let visuals breathe | shipped |
|
|
26
|
+
| `svg-diagrams.md` | Inline-SVG authoring rules and pitfalls (`<tspan>` not `<b>`, no `var()` in SVG attrs, both-theme colors) | shipped |
|
|
27
|
+
| `code-translation.md` | Code ↔ plain-English translation (rules + zero-JS comments-on-top panel) | shipped |
|
|
28
|
+
| `quiz-patterns.md` | Quiz design (coverage rule, application-not-memory) + zero-JS reveal quiz | shipped |
|
|
29
|
+
| `diagrams.md` | HTML/CSS flow + annotated file-tree diagram idioms | shipped |
|
|
30
|
+
|
|
31
|
+
## Plugin-owned versioned chrome (indexed here, lives with its owner)
|
|
32
|
+
|
|
33
|
+
Some shared visual machinery is too heavy and too domain-specific to vendor into every plugin,
|
|
34
|
+
but still needs one canonical, versioned home. Those modules stay in their owning plugin and are
|
|
35
|
+
*indexed* here so "where does shared visual machinery live" has a single answer:
|
|
36
|
+
|
|
37
|
+
| Chrome | Canonical source | Version | Convention |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| Course chrome (`styles.css`, `main.js`, `_footer.html`, `build.sh`, `validate.mjs`) | `codebase-to-course/skills/codebase-to-course/references/` | `chrome v2 — inline translation engine (comments-on-top)` | `docs/skill-patterns.md` "Versioned course chrome" |
|
|
40
|
+
|
|
41
|
+
The rules that keep it from fossilizing: rendering files open with the version stamp (no stamp
|
|
42
|
+
= v1, the retired side-by-side renderer); `validate.mjs` carries `CHROME_VERSION`, and both
|
|
43
|
+
`build.sh` and the course gate fail unstamped or version-mixed copies; `build.sh` refreshes the
|
|
44
|
+
chrome from the canonical source whenever `CLAUDE_PLUGIN_ROOT`/`C2C_REFERENCES` resolves.
|
|
45
|
+
Vendored course copies are build artifacts, never templates.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Code ↔ plain-English translation
|
|
2
|
+
|
|
3
|
+
The single most valuable tool for teaching code to non-technical readers: never show code
|
|
4
|
+
without a plain-English translation of what each line does, and never write a paragraph
|
|
5
|
+
*about* code when you can annotate the code itself. Originally codebase-to-course's pattern.
|
|
6
|
+
|
|
7
|
+
**Graceful degradation:** if this module isn't available, follow the code with a short
|
|
8
|
+
"in plain English:" list — one bullet per line that matters.
|
|
9
|
+
|
|
10
|
+
## The rules (medium-independent)
|
|
11
|
+
|
|
12
|
+
- **Use real code, verbatim.** Never modify, simplify, or trim snippets — the reader should
|
|
13
|
+
be able to open the file and see exactly what they learned from. *Choose* naturally short,
|
|
14
|
+
punchy snippets (5–10 lines) instead of butchering long ones.
|
|
15
|
+
- **No horizontal scrollbars, ever.** `white-space: pre-wrap; word-break: break-word` — this
|
|
16
|
+
is teaching material, not an IDE; readability beats preserved indentation.
|
|
17
|
+
- **One note per line that matters,** in everyday language, positioned so the pairing is
|
|
18
|
+
unmistakable. Skip noise lines (braces, imports) rather than annotating them emptily.
|
|
19
|
+
- A metaphor inside a note is welcome; jargon inside a note defeats the point (gloss it —
|
|
20
|
+
see `tooltip.md`).
|
|
21
|
+
|
|
22
|
+
## Portable snippet (decks, briefings — zero JS)
|
|
23
|
+
|
|
24
|
+
Comments-on-top: each plain-English note is a styled line directly ABOVE the code it
|
|
25
|
+
explains, inside one token-styled panel. Inherits the consumer's palette.
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<style>
|
|
29
|
+
.translate{background:var(--ink);color:var(--bg);border-radius:12px;padding:1em 1.2em;
|
|
30
|
+
font-family:var(--mono);font-size:.9em;line-height:1.6;
|
|
31
|
+
white-space:pre-wrap;word-break:break-word}
|
|
32
|
+
.translate .note{display:block;color:var(--accent2);font-family:var(--sans);
|
|
33
|
+
font-style:italic;filter:brightness(1.25)}
|
|
34
|
+
</style>
|
|
35
|
+
|
|
36
|
+
<pre class="translate"><span class="note">// Ask the server for this user's saved items…</span>
|
|
37
|
+
const res = await fetch(`/api/favorites/${userId}`);
|
|
38
|
+
<span class="note">// …and unpack the JSON body once it arrives.</span>
|
|
39
|
+
const favorites = await res.json();</pre>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Deck usage: give the translation its own slide (shared pedagogy — one idea per slide); the
|
|
43
|
+
deck's 30px body type means 4–8 code lines fill a slide comfortably.
|
|
44
|
+
|
|
45
|
+
## In codebase-to-course
|
|
46
|
+
|
|
47
|
+
Courses do NOT use this snippet — the prebuilt assets ship a far richer engine (side-by-side
|
|
48
|
+
panels rebuilt as comments-on-top at render time, syntax highlighting, per-block "hide notes"
|
|
49
|
+
toggle). Author against the `.translation-block` contract in the skill's
|
|
50
|
+
`interactive-elements.md`; this module is the portable variant for every other surface.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# HTML/CSS diagram idioms
|
|
2
|
+
|
|
3
|
+
Diagrams that are plain HTML + CSS (boxes, arrows, trees — often with an emoji as the icon)
|
|
4
|
+
inherit the theme tokens, wrap responsively, and never hit SVG's text pitfalls. This is
|
|
5
|
+
codebase-to-course's native diagram idiom, portable to any praxis surface. For hand-drawn
|
|
6
|
+
SVG figures instead, see `svg-diagrams.md`.
|
|
7
|
+
|
|
8
|
+
**Graceful degradation:** if this module isn't available, a numbered list of steps or an
|
|
9
|
+
indented mono file listing does the job.
|
|
10
|
+
|
|
11
|
+
## Flow of steps (boxes with arrow separators)
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<style>
|
|
15
|
+
.flow{display:flex;align-items:stretch;gap:0;flex-wrap:wrap}
|
|
16
|
+
.flow .step{flex:1;min-width:9em;background:var(--card);border:1px solid var(--line);
|
|
17
|
+
border-radius:12px;padding:.8em 1em;text-align:center}
|
|
18
|
+
.flow .step b{display:block;margin-bottom:.25em}
|
|
19
|
+
.flow .arrow{align-self:center;padding:0 .5em;color:var(--muted);font-size:1.4em}
|
|
20
|
+
</style>
|
|
21
|
+
|
|
22
|
+
<div class="flow">
|
|
23
|
+
<div class="step">📨 <b>Input</b><span class="muted">the click arrives</span></div>
|
|
24
|
+
<div class="arrow">→</div>
|
|
25
|
+
<div class="step">⚙️ <b>Process</b><span class="muted">the server decides</span></div>
|
|
26
|
+
<div class="arrow">→</div>
|
|
27
|
+
<div class="step">📬 <b>Output</b><span class="muted">the page updates</span></div>
|
|
28
|
+
</div>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Annotated file tree
|
|
32
|
+
|
|
33
|
+
Use instead of paragraphs saying "this folder does X, that folder does Y":
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<style>
|
|
37
|
+
.tree{font-family:var(--mono);background:var(--chip);border:1px solid var(--line);
|
|
38
|
+
border-radius:12px;padding:1em 1.2em;line-height:1.8;white-space:pre}
|
|
39
|
+
.tree .why{font-family:var(--sans);color:var(--muted);font-style:italic}
|
|
40
|
+
</style>
|
|
41
|
+
|
|
42
|
+
<div class="tree">src/
|
|
43
|
+
├─ api/ <span class="why">← every route the frontend can call</span>
|
|
44
|
+
├─ models/ <span class="why">← what the data looks like</span>
|
|
45
|
+
└─ jobs/ <span class="why">← work that runs on a schedule</span></div>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Rules: label every box/branch with WHY it matters, not just its name; keep a flow to 3–5
|
|
49
|
+
steps (split longer chains — shared pedagogy: one idea per screen); emoji are icons, pick
|
|
50
|
+
one each, don't decorate.
|
|
51
|
+
|
|
52
|
+
## In codebase-to-course
|
|
53
|
+
|
|
54
|
+
Courses get richer, *animated* versions from the prebuilt assets (flow animations, group-chat
|
|
55
|
+
visualizations, interactive architecture diagrams) — author against the contracts in the
|
|
56
|
+
skill's `interactive-elements.md`. These static idioms are the portable variants for decks
|
|
57
|
+
and briefings.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Visual-teaching principles
|
|
2
|
+
|
|
3
|
+
The shared pedagogy behind every praxis teaching surface — codebase-to-course's course screens,
|
|
4
|
+
educate's deck slides, research's briefing pages. Written once here; each skill keeps only its
|
|
5
|
+
medium-specific rules (and a one-line distillation as fallback) and points at this module.
|
|
6
|
+
|
|
7
|
+
**Graceful degradation:** if this module isn't available, the one-liner to keep: *one idea per
|
|
8
|
+
screen; show, don't tell; lead with the point; split, don't shrink.*
|
|
9
|
+
|
|
10
|
+
## The principles
|
|
11
|
+
|
|
12
|
+
1. **One idea per screen/slide.** Each unit of display teaches exactly one thing. If it feels
|
|
13
|
+
crammed, the fix is always MORE screens/slides, never smaller type or denser text. A
|
|
14
|
+
20-slide deck that's effortless beats a 6-slide deck you squint at.
|
|
15
|
+
|
|
16
|
+
2. **Show, don't tell.** Eyes glaze over text blocks. Prefer a diagram, card row, flow, code
|
|
17
|
+
translation, or animation to a paragraph — if you're writing a fourth sentence, stop and
|
|
18
|
+
convert it into a visual. Aim closer to an infographic than a textbook.
|
|
19
|
+
|
|
20
|
+
3. **Lead with the point.** State the claim/verdict/takeaway first, then justify it. A briefing
|
|
21
|
+
leads with the analysis's verdict; a deck leads with the lesson's claim; a course screen
|
|
22
|
+
leads with the concept, not the buildup. Don't make the reader reverse-engineer the answer.
|
|
23
|
+
|
|
24
|
+
4. **Split, don't shrink.** Never reduce type, tighten spacing, or trim whitespace to make
|
|
25
|
+
content fit. Cut words, or add another screen/slide. Readability is the budget everything
|
|
26
|
+
else pays from.
|
|
27
|
+
|
|
28
|
+
5. **Let visuals breathe.** A figure that matters gets the room it needs — its own slide, a
|
|
29
|
+
full-width screen block, generous whitespace around it. Alternate big visuals with narrow
|
|
30
|
+
text to create rhythm; don't wedge a diagram into a corner.
|
|
31
|
+
|
|
32
|
+
## How each medium applies them
|
|
33
|
+
|
|
34
|
+
- **Course screens** (codebase-to-course): every screen ≥50% visual, max 2–3 sentences per text
|
|
35
|
+
block, one "hero visual" per module — see the skill's `content-philosophy.md`.
|
|
36
|
+
- **Deck slides** (educate): large type is non-negotiable; verify nothing clips at 16:9 — see
|
|
37
|
+
the deck template's header rules.
|
|
38
|
+
- **Briefing pages** (research): verdict first, every figure traces to the analysis — see
|
|
39
|
+
`artifact-layer.md`.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Quiz patterns — test application, not memory
|
|
2
|
+
|
|
3
|
+
A quiz exists to check whether the learner can *use* what they learned on a new problem, not
|
|
4
|
+
whether they can regurgitate a definition. Originally codebase-to-course's quiz doctrine;
|
|
5
|
+
the design rules are medium-independent.
|
|
6
|
+
|
|
7
|
+
**Graceful degradation:** if this module isn't available, end the lesson/module with 2–3
|
|
8
|
+
"what would you do?" questions in plain prose and discuss the answers.
|
|
9
|
+
|
|
10
|
+
## The coverage rule (hard requirement)
|
|
11
|
+
|
|
12
|
+
A question may only use terms, concepts, components, and scenarios the material has already
|
|
13
|
+
introduced *before* the quiz. Applying knowledge to a brand-new situation is the whole point —
|
|
14
|
+
but every term the question leans on must be 100% guaranteed already taught. A learner should
|
|
15
|
+
never hit a quiz and think "we never talked about this." If a great question needs an
|
|
16
|
+
unintroduced concept, teach it first or cut the question.
|
|
17
|
+
|
|
18
|
+
## What to quiz (in order of value)
|
|
19
|
+
|
|
20
|
+
1. **"What would you do?" scenarios** — a new situation, apply what you learned. The gold standard.
|
|
21
|
+
2. **Debugging scenarios** — "X is broken; where would you look first?"
|
|
22
|
+
3. **Architecture decisions** — "would you put this logic here or there — and why?"
|
|
23
|
+
4. **Tracing exercises** — "when a user does X, what path does the data take?"
|
|
24
|
+
|
|
25
|
+
**Never quiz:** definitions (that's what glossing is for), file-name recall, syntax details,
|
|
26
|
+
or anything answerable by scrolling up and copying.
|
|
27
|
+
|
|
28
|
+
## Tone
|
|
29
|
+
|
|
30
|
+
Wrong answers get encouraging explanations that teach something new ("Not quite — here's
|
|
31
|
+
why…"); right answers get brief reinforcement of the principle ("Exactly — this works
|
|
32
|
+
because…"). Never punitive, never score-focused.
|
|
33
|
+
|
|
34
|
+
## Portable snippet (decks, briefings — zero JS)
|
|
35
|
+
|
|
36
|
+
`<details>`/`<summary>` gives tap-to-reveal answers with no script, keyboard-accessible,
|
|
37
|
+
token-styled:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<style>
|
|
41
|
+
.quiz details{border:1px solid var(--line);border-radius:10px;margin:.4em 0;
|
|
42
|
+
background:var(--chip)}
|
|
43
|
+
.quiz summary{cursor:pointer;padding:.55em .9em;font-weight:600}
|
|
44
|
+
.quiz details p{margin:0;padding:.2em .9em .7em;color:var(--muted)}
|
|
45
|
+
.quiz details.right summary{border-left:4px solid var(--accent2)}
|
|
46
|
+
</style>
|
|
47
|
+
|
|
48
|
+
<div class="quiz">
|
|
49
|
+
<p><b>You want to add a "save to favorites" feature. Where does the logic live?</b></p>
|
|
50
|
+
<details><summary>A. In the browser extension</summary>
|
|
51
|
+
<p>Not quite — the extension only displays data; it never owns it. Look one hop back.</p></details>
|
|
52
|
+
<details class="right"><summary>B. In the backend API</summary>
|
|
53
|
+
<p>Exactly — the API owns the data, so a new feature that stores state starts there.</p></details>
|
|
54
|
+
</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Deck usage: one question per slide, reveal by tapping — a natural end-of-lesson slide before
|
|
58
|
+
the takeaways.
|
|
59
|
+
|
|
60
|
+
## In codebase-to-course
|
|
61
|
+
|
|
62
|
+
Courses do NOT use this snippet — the prebuilt assets ship graded quiz engines
|
|
63
|
+
(multiple-choice, drag-and-drop, spot-the-bug, scenario) with per-answer explanations; author
|
|
64
|
+
against the contracts in the skill's `interactive-elements.md`. The coverage rule above
|
|
65
|
+
applies everywhere.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Inline-SVG diagram rules
|
|
2
|
+
|
|
3
|
+
Hand-drawn inline SVG is the praxis idiom for explanatory figures (educate decks draw
|
|
4
|
+
diagrams this way; research briefings draw charts as inline SVG). SVG has sharp edges that
|
|
5
|
+
HTML habits walk straight into — these are the hard-won rules, originally from educate's
|
|
6
|
+
deck template.
|
|
7
|
+
|
|
8
|
+
**Graceful degradation:** if this module isn't available, the two rules to keep: *no
|
|
9
|
+
`<b>`/`<i>` inside SVG `<text>` (use `<tspan>`), and CSS `var(--…)` does not resolve in SVG
|
|
10
|
+
presentation attributes — use literal values.*
|
|
11
|
+
|
|
12
|
+
## The pitfalls (hard rules)
|
|
13
|
+
|
|
14
|
+
1. **Never put `<b>` or `<i>` inside an SVG `<text>` element.** HTML formatting tags are
|
|
15
|
+
invisible-but-broken inside SVG — the text silently fails to render or renders unstyled.
|
|
16
|
+
Use `<tspan font-weight="700">` / `<tspan font-style="italic">` instead.
|
|
17
|
+
|
|
18
|
+
2. **CSS `var(--token)` does NOT resolve in SVG presentation attributes** (`fill="var(--accent)"`
|
|
19
|
+
silently paints black). Use **literal color values** in SVG attributes — and since literals
|
|
20
|
+
can't follow the theme, pick colors that read on BOTH light and dark backgrounds, or give
|
|
21
|
+
the figure its own contrasting panel. (`var()` does work in a `style=""` attribute or CSS
|
|
22
|
+
rule targeting SVG elements — but only for properties CSS controls, and it's easy to get
|
|
23
|
+
this half-right; literals are the predictable path.)
|
|
24
|
+
|
|
25
|
+
3. **Size for reflow:** `width:100%; height:auto` on the `<svg>` plus a `viewBox` — never fixed
|
|
26
|
+
pixel width/height attributes alone, or the figure won't scale with its container.
|
|
27
|
+
|
|
28
|
+
4. **Accessibility:** `role="img"` and an `aria-label` describing what the diagram shows.
|
|
29
|
+
|
|
30
|
+
5. **Give the figure room** (shared pedagogy — "let visuals breathe"): a diagram that carries
|
|
31
|
+
the point gets its own slide / a full-width block, filling most of the available width and
|
|
32
|
+
height. Don't wedge it beside a paragraph.
|
|
33
|
+
|
|
34
|
+
## Skeleton — a boxes-and-arrows flow
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<svg viewBox="0 0 800 300" role="img" aria-label="input flows through process to output">
|
|
38
|
+
<rect x="40" y="110" width="200" height="80" rx="12" fill="#eef0ff" stroke="#4f46e5" stroke-width="2"/>
|
|
39
|
+
<text x="140" y="158" text-anchor="middle" font-size="26" fill="#1b2733" font-family="sans-serif">Input</text>
|
|
40
|
+
<line x1="240" y1="150" x2="320" y2="150" stroke="#5a6776" stroke-width="3" marker-end="url(#a)"/>
|
|
41
|
+
<rect x="320" y="110" width="200" height="80" rx="12" fill="#eefaf7" stroke="#0e9488" stroke-width="2"/>
|
|
42
|
+
<text x="420" y="158" text-anchor="middle" font-size="26" fill="#1b2733" font-family="sans-serif">Process</text>
|
|
43
|
+
<defs><marker id="a" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
|
|
44
|
+
<path d="M0,0 L8,3 L0,6 Z" fill="#5a6776"/></marker></defs>
|
|
45
|
+
</svg>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Notes: pastel fills + saturated strokes read on both themes; the `<marker>` def gives every
|
|
49
|
+
arrow a clean head — define it once per SVG and reference with `marker-end="url(#a)"`; keep
|
|
50
|
+
marker `id`s unique per page if a page holds several SVGs.
|
|
51
|
+
|
|
52
|
+
## Who uses this
|
|
53
|
+
|
|
54
|
+
- **educate decks** — the native idiom; the deck template's header carries the one-line
|
|
55
|
+
distillation and a sample diagram slide.
|
|
56
|
+
- **research briefings** — charts are inline SVG; these rules apply on top of the `dataviz`
|
|
57
|
+
skill's chart guidance.
|
|
58
|
+
- **codebase-to-course** — courses prefer the HTML/CSS + emoji diagram patterns in the skill's
|
|
59
|
+
`interactive-elements.md`; when a course page does hand-draw inline SVG, these rules apply.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Jargon glossary tooltip
|
|
2
|
+
|
|
3
|
+
Gloss every technical term where it appears, so the reader never has to leave the page (or
|
|
4
|
+
ask) to decode jargon. The definition rides a `data-tip` attribute; a popover shows it on
|
|
5
|
+
hover (desktop) or tap (touch). This is the single most valuable accessibility feature for
|
|
6
|
+
non-technical audiences — codebase-to-course's content rules call it "No Term Left Behind."
|
|
7
|
+
|
|
8
|
+
**Graceful degradation:** if this module isn't available, gloss terms in parentheses on
|
|
9
|
+
first use — `the manifest (the file that declares what the extension may do)` — and move on.
|
|
10
|
+
|
|
11
|
+
## The contract
|
|
12
|
+
|
|
13
|
+
Any element — HTML *or* SVG — may carry a `data-tip` attribute with a 1–2 sentence,
|
|
14
|
+
everyday-language definition:
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<span data-tip="A service worker is a background script that runs independently of the
|
|
18
|
+
page — a behind-the-scenes assistant that's on even when you're not looking.">service
|
|
19
|
+
worker</span>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Give the reader a **visible affordance** so glossed terms are discoverable: a dashed
|
|
23
|
+
underline, or a small `?` badge (educate's decks use the badge — see the deck template's
|
|
24
|
+
sample slide).
|
|
25
|
+
- Keep definitions to 1–2 sentences, everyday language; a metaphor helps.
|
|
26
|
+
- Gloss on **first use per screen/slide**, not every repetition.
|
|
27
|
+
|
|
28
|
+
## The snippet
|
|
29
|
+
|
|
30
|
+
Self-contained, and written against the **shared token names** (`--ink`, `--bg`) so it
|
|
31
|
+
inherits the consuming plugin's palette in both light and dark themes (the bubble is
|
|
32
|
+
inverted-contrast: dark-on-light pages get a dark bubble and vice versa). Popover text size
|
|
33
|
+
follows `--tip-size` (default `1rem`) — decks set it larger. The popover is appended to
|
|
34
|
+
`document.body` with `position: fixed`, so it is **never clipped** by an ancestor's
|
|
35
|
+
`overflow: hidden`, and it hides when printing.
|
|
36
|
+
|
|
37
|
+
Consumers embed the marked regions verbatim; `scripts/sync-shared.mjs` re-stamps them (drift
|
|
38
|
+
fails the test suite). Hand-edit only here.
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
/* praxis:tooltip-css:start */
|
|
42
|
+
[data-tip]{cursor:help}
|
|
43
|
+
.tip-pop{position:fixed;z-index:1000;max-width:min(420px,80vw);background:var(--ink);color:var(--bg);
|
|
44
|
+
font-size:var(--tip-size,1rem);line-height:1.4;padding:.65em .9em;border-radius:10px;
|
|
45
|
+
pointer-events:none;box-shadow:0 8px 24px rgba(0,0,0,.28);opacity:0;transform:translateY(4px);
|
|
46
|
+
transition:opacity .12s ease,transform .12s ease}
|
|
47
|
+
.tip-pop.show{opacity:1;transform:translateY(0)}
|
|
48
|
+
@media print{ .tip-pop{display:none} }
|
|
49
|
+
/* praxis:tooltip-css:end */
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
// praxis:tooltip-js:start
|
|
54
|
+
// One shared popover: follows the cursor on hover, toggles on click/tap (touch), clamps to
|
|
55
|
+
// the viewport, and closes on tap-away. Works for HTML and SVG [data-tip] elements alike.
|
|
56
|
+
(function(){
|
|
57
|
+
const pop=document.createElement('div'); pop.className='tip-pop'; document.body.appendChild(pop);
|
|
58
|
+
let shownFor=null;
|
|
59
|
+
const place=(x,y)=>{ const pad=14, r=pop.getBoundingClientRect();
|
|
60
|
+
let px=x+pad, py=y+pad;
|
|
61
|
+
if(px+r.width>innerWidth-8) px=Math.max(8,x-r.width-pad);
|
|
62
|
+
if(py+r.height>innerHeight-8) py=Math.max(8,y-r.height-pad);
|
|
63
|
+
pop.style.left=px+'px'; pop.style.top=py+'px'; };
|
|
64
|
+
const show=(t,x,y)=>{ pop.textContent=t.getAttribute('data-tip'); pop.classList.add('show'); shownFor=t; place(x,y); };
|
|
65
|
+
const hide=()=>{ pop.classList.remove('show'); shownFor=null; };
|
|
66
|
+
document.addEventListener('mouseover',e=>{ const t=e.target.closest&&e.target.closest('[data-tip]'); if(t) show(t,e.clientX,e.clientY); });
|
|
67
|
+
document.addEventListener('mousemove',e=>{ if(shownFor) place(e.clientX,e.clientY); });
|
|
68
|
+
document.addEventListener('mouseout',e=>{ const t=e.target.closest&&e.target.closest('[data-tip]'); if(t && !t.contains(e.relatedTarget)) hide(); });
|
|
69
|
+
document.addEventListener('click',e=>{ const t=e.target.closest&&e.target.closest('[data-tip]');
|
|
70
|
+
if(t){ e.stopPropagation(); (shownFor===t)?hide():show(t,e.clientX,e.clientY); } else hide(); });
|
|
71
|
+
})();
|
|
72
|
+
// praxis:tooltip-js:end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Where each plugin gets it
|
|
76
|
+
|
|
77
|
+
- **educate decks** — the deck template carries the stamped regions; nothing to author.
|
|
78
|
+
- **research briefings** — copy both regions into the page (alongside the base.html blocks).
|
|
79
|
+
- **codebase-to-course pages** — courses do NOT use this snippet: the prebuilt assets
|
|
80
|
+
(`references/styles.css` + `references/main.js`, copied verbatim into every course) ship
|
|
81
|
+
the same pattern as a course-native engine with the `.term` + `data-definition` contract
|
|
82
|
+
and a dashed-underline affordance. Author course HTML against that contract and never
|
|
83
|
+
inline tooltip CSS/JS there (see the skill's `interactive-elements.md`).
|