Skip to content

Commit c4ebb45

Browse files
docs(guides): core guides — authoring, templates & theming, Gnosis, accessibility (#46)
## What The core user guides (`docs/guides/`), in the same example-led voice as the README: - **`authoring-a-site.adoc`** — source layout, front-matter, building, output structure (clean URLs, collections, tags), feeds/sitemap, drafts, `public/`, `site.conf`. - **`templates-and-theming.adoc`** — layout resolution, partials, the full template-variable table, assets, the built-in accessible theme. - **`the-gnosis-layer.adoc`** — `(:placeholder)` + filter chains, DAX `{{#if}}`/`{{#for}}`, `FlexiText`, the `descriptiles` source (and an honest note on the current `.scm` gap). - **`accessibility.adoc`** — engine-owned vs author-owned, a WCAG-mapping table, the `echo-types` proof direction. All four cross-link each other and the README / EXPLAINME / ADR-0001; written to match the **actual built binary** (no aspirational features presented as present). ## Sequence (remaining docs) Next batches: `docs/reference/` (CLI, template-vars, `site.conf`), a `deploying.adoc` (Pages), reconcile `PHASE2-ROADMAP`/`ROADMAP` with ADR-0001, resolve the `CONTRIBUTING.adoc`/`.md` duplicate, then the **wiki** (separate repo — staged in `docs/wiki/` for you to copy across). ## Notes - Docs-only; inherits the two standing `main` reds (Pandoc build; Hypatia findings) — neither introduced here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WN6nPWWe4dvXfoqBG6auWN --- _Generated by [Claude Code](https://claude.ai/code/session_01WN6nPWWe4dvXfoqBG6auWN)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent e597ff1 commit c4ebb45

4 files changed

Lines changed: 369 additions & 0 deletions

File tree

docs/guides/accessibility.adoc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Accessibility
4+
:toc: left
5+
:toclevels: 2
6+
7+
casket treats accessibility as a property of the *engine*, not a checklist for
8+
the author. This guide separates what casket guarantees from what remains your
9+
responsibility.
10+
11+
== Engine-owned (casket guarantees)
12+
13+
When you use the built-in template and the default pipeline, casket emits:
14+
15+
[cols="2,3"]
16+
|===
17+
| What | WCAG relevance
18+
19+
| `<html lang="…">`, `<meta viewport>`, `color-scheme: light dark`
20+
| 3.1.1 Language of Page; 1.4.10 Reflow; respects OS light/dark
21+
22+
| A skip link (`Skip to content`) and labelled landmarks
23+
(`header` / `nav[aria-label]` / `main#main` / `footer`)
24+
| 2.4.1 Bypass Blocks; 1.3.1 Info & Relationships
25+
26+
| `scope="col"` added to Pandoc table headers automatically
27+
| 1.3.1 Info & Relationships (screen-reader table navigation)
28+
29+
| Valid `<time datetime="…">` (empty `<time>` is dropped)
30+
| 1.3.1; avoids invalid markup
31+
32+
| Mandatory alt-text on every generated badge (`FlexiText`)
33+
| 1.1.1 Non-text Content — *enforced at construction*, cannot be omitted
34+
|===
35+
36+
The `FlexiText` guarantee is structural: the smart constructor rejects empty
37+
alt-text, so a badge without a text alternative cannot be built. See
38+
link:the-gnosis-layer.adoc[The Gnosis layer].
39+
40+
== Author-owned (your responsibility)
41+
42+
casket cannot guarantee accessibility for content and templates you author:
43+
44+
* *Your images* — provide real alt text (`![meaningful description](img.png)`).
45+
* *Custom templates* — if you replace the built-in template, keep the `lang`
46+
attribute, skip link, landmarks, and a logical, unskipped heading order.
47+
* *Custom CSS* — maintain colour contrast (aim ≥ 4.5:1 body text), visible
48+
`:focus-visible`, ≥ 44px touch targets, and honour `prefers-reduced-motion`.
49+
* *Link text* — write meaningful link text, not "click here".
50+
* *Headings* — one `<h1>` per page; don't skip levels.
51+
52+
== Verifying
53+
54+
The engine-owned guarantees ship by default; for the author-owned criteria, run
55+
an automated checker (axe, pa11y, or Lighthouse) over the built `_site/` in CI.
56+
The sibling project https://github.com/hyperpolymath/ddraig-ssg[ddraig] maintains
57+
an `ACCESSIBILITY-CHECKLIST` enumerating engine-owned vs author-owned success
58+
criteria; casket shares that split.
59+
60+
== Direction
61+
62+
The accessibility invariant is being lifted from a constructor guarantee toward
63+
a *proof*: the loss-with-residue `echo-types` `Echo` formalism, where rendered
64+
output retains a witness of its accessible source. `FlexiText` is the current
65+
unverified shadow of that; the dependently-typed sibling ddraig owns the native
66+
proof. See link:../decisions/ADR-0001-content-engine.adoc[ADR-0001].

docs/guides/authoring-a-site.adoc

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Authoring a site with casket
4+
:toc: left
5+
:toclevels: 2
6+
7+
How to write content, build it, and understand the output. For customising the
8+
look see link:templates-and-theming.adoc[Templates & theming]; for the metadata
9+
layer see link:the-gnosis-layer.adoc[The Gnosis layer].
10+
11+
== Source layout
12+
13+
A site is a directory of Markdown plus optional supporting folders:
14+
15+
[source]
16+
----
17+
my-site/
18+
├── content/ # your pages (passed as the build input dir)
19+
│ ├── index.md
20+
│ └── posts/
21+
│ └── 2026-06-23-hello.md
22+
├── templates/ # optional: layouts + partials (theming guide)
23+
│ ├── default.html
24+
│ └── partials/
25+
├── assets/ # optional: copied to /assets in the output
26+
├── public/ # optional: copied verbatim to the OUTPUT ROOT
27+
└── site.conf # optional: site-wide {{site.*}} values
28+
----
29+
30+
Only `content/` (the input directory) is required; everything else has sane
31+
defaults — including a built-in accessible template.
32+
33+
== Writing a page
34+
35+
Each page is Markdown with a `---`-fenced front-matter block:
36+
37+
[source,markdown]
38+
----
39+
---
40+
title: Hello, world
41+
date: 2026-06-23
42+
description: A first page built with casket.
43+
tags: [intro, meta]
44+
---
45+
# Hello
46+
47+
Body text in **Markdown**, rendered by Pandoc: tables, footnotes,
48+
task lists, fenced + highlighted code, smart punctuation.
49+
----
50+
51+
A leading SPDX HTML comment before the `---` fence is tolerated.
52+
53+
Recognised keys: `title`, `date`, `description`, `tags`, `layout`, `draft`,
54+
`slug`, and `site`/`brand`. Any *other* key is exposed to templates as
55+
`{{key}}`, so front-matter doubles as page-scoped template data.
56+
57+
== Building
58+
59+
[source,bash]
60+
----
61+
casket-ssg build content _site # input dir, then optional output dir (default _site)
62+
casket-ssg build --drafts content # include `draft: true` pages (or set CASKET_DRAFTS=1)
63+
casket-ssg build --no-clean-urls content
64+
casket-ssg clean _site # remove an output directory
65+
----
66+
67+
== Output structure
68+
69+
By default casket emits *clean URLs*:
70+
71+
[cols="2,2"]
72+
|===
73+
| Source | Output
74+
75+
| `content/index.md` | `_site/index.html`
76+
| `content/about.md` | `_site/about/index.html` (→ `/about/`)
77+
| `content/posts/2026-…-hello.md` | `_site/posts/2026-…-hello/index.html`
78+
|===
79+
80+
* `index.md` always maps to `index.html` in the same directory.
81+
* `slug: my-slug` front-matter overrides the output basename.
82+
* `--no-clean-urls` emits `about.html` instead of `about/index.html`.
83+
84+
== Collections
85+
86+
Any source directory with *two or more dated pages* and no `index.md` of its own
87+
gets a generated listing page (newest first). So `content/posts/` with several
88+
dated posts yields `/posts/` automatically — no configuration.
89+
90+
== Tags
91+
92+
Pages with `tags:` produce a page per tag at `/tags/<tag>/` (slugified) plus a
93+
`/tags/` index counting each tag.
94+
95+
== Feeds & discovery
96+
97+
casket writes, from `site.baseurl`:
98+
99+
* `sitemap.xml` — every page URL (including generated collection/tag pages);
100+
* `feed.xml` — an Atom feed of the 20 most recent *dated* pages.
101+
102+
== Drafts
103+
104+
`draft: true` pages are skipped unless you pass `--drafts` (or set
105+
`CASKET_DRAFTS=1`). Skipped drafts are reported during the build.
106+
107+
== Verbatim files (`public/`)
108+
109+
Anything under `<input>/public/` is copied to the *output root*, so
110+
`public/CNAME`, `public/.well-known/security.txt`, `public/robots.txt`, and
111+
`public/favicon.svg` ship at `/CNAME`, `/.well-known/…`, etc.
112+
113+
== Site configuration (`site.conf`)
114+
115+
A simple `key = value` file (looked up in the CWD, then the input dir), exposed
116+
to templates as `{{site.*}}`:
117+
118+
[source,ini]
119+
----
120+
title = My Casket Site
121+
baseurl = https://example.com
122+
author = Jane Doe
123+
description = Built with casket
124+
nav = Home=/,Posts=/posts/,About=/about/
125+
----
126+
127+
`nav` (optional) drives the primary navigation; without it, casket derives nav
128+
from your top-level pages.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Templates & theming
4+
:toc: left
5+
:toclevels: 2
6+
7+
How casket turns a rendered page into HTML: layout resolution, partials, the
8+
template variables you can use, and the built-in accessible theme. See also
9+
link:authoring-a-site.adoc[Authoring] and
10+
link:the-gnosis-layer.adoc[The Gnosis layer].
11+
12+
== Layout resolution
13+
14+
For each page casket picks a layout in this order:
15+
16+
. `templates/<layout>.html` — where `<layout>` is the page's `layout:`
17+
front-matter (a bare name gets `.html` appended);
18+
. `templates/default.html` — the site default;
19+
. the *built-in accessible template* — if no `templates/` file exists.
20+
21+
So you can ship zero templates and still get a complete, accessible page.
22+
23+
== Partials
24+
25+
Inside any template, include a partial from `templates/partials/<name>.html`:
26+
27+
[source,html]
28+
----
29+
{{> header}}
30+
{{include:footer}}
31+
----
32+
33+
Both spellings work. Partials may include partials (guarded against cycles). A
34+
missing partial renders an HTML comment rather than failing the build.
35+
36+
== Template variables
37+
38+
Substituted by `fillTemplate`, in this order (so body HTML is never
39+
re-processed):
40+
41+
[cols="1,3"]
42+
|===
43+
| Variable | Value
44+
45+
| `{{site.KEY}}` | Any key from `site.conf` (e.g. `{{site.title}}`, `{{site.baseurl}}`)
46+
| `{{KEY}}` | Any front-matter key on the page (arbitrary custom keys included)
47+
| `{{title}}` | Resolved page title (front-matter `title`, else the file base name)
48+
| `{{content}}` | The rendered HTML body (substituted *last*)
49+
| `{{toc}}` | Table-of-contents `<nav>` from the page headings
50+
| `{{nav}}` | Primary navigation (`site.conf` `nav`, else derived)
51+
| `{{date}}` | Page `date` (empty for generated pages)
52+
| `{{description}}`| Page `description`, else `site.description`
53+
| `{{brand}}` | Page `site`/`brand`, else the site title
54+
|===
55+
56+
The idiom `<time>{{date}}</time>` is rewritten to a valid
57+
`<time datetime="…">…</time>` (or dropped when there is no date), so empty
58+
`<time>` elements never ship.
59+
60+
== Assets
61+
62+
These directories, if present in the CWD, are copied into the output:
63+
`assets/`, `static/`, `css/`, `js/`, `images/`. casket additionally writes the
64+
syntax-highlighting stylesheet to `/assets/highlight.css` — link it from your
65+
template:
66+
67+
[source,html]
68+
----
69+
<link rel="stylesheet" href="/assets/style.css">
70+
<link rel="stylesheet" href="/assets/highlight.css">
71+
----
72+
73+
(For files that must land at the site root, use `public/` — see
74+
link:authoring-a-site.adoc[Authoring].)
75+
76+
== The built-in accessible theme
77+
78+
When you provide no template, casket's fallback is accessible by construction:
79+
`<html lang>`, a skip link, labelled `header`/`nav`/`main#main`/`footer`
80+
landmarks, `<meta viewport>`, and `color-scheme: light dark`. casket also adds
81+
`scope="col"` to Pandoc's table headers (WCAG 1.3.1).
82+
83+
If you author your *own* template, you own its accessibility — keep the skip
84+
link, landmarks, `lang`, and a logical heading order. See
85+
link:accessibility.adoc[the accessibility guide] for the engine-owned vs
86+
author-owned split.

docs/guides/the-gnosis-layer.adoc

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= The Gnosis layer
4+
:toc: left
5+
:toclevels: 2
6+
7+
Gnosis is casket's optional metadata-driven rendering layer. It runs over the
8+
Markdown *before* Pandoc, so you can substitute typed values, branch and loop on
9+
metadata, and emit accessible badges — all from a structured data context. See
10+
link:authoring-a-site.adoc[Authoring] for the basics.
11+
12+
== The context
13+
14+
Gnosis renders against a *context* — a map of keys to `FlexiText` values, where
15+
each value pairs a *visual* with mandatory *alt-text*. The context is built from:
16+
17+
* the page's front-matter (highest priority), merged over
18+
* the repository's descriptive metadata (the six `.a2ml` files under
19+
`.machine_readable/descriptiles/`).
20+
21+
[NOTE]
22+
====
23+
*Current state:* the loader (`Gnosis.SixSCM`) still reads the older flat
24+
`.machine_readable/*.scm` layout, so today the context is driven mainly by
25+
front-matter. Wiring it to `descriptiles/*.a2ml` is the first migration step in
26+
link:../decisions/ADR-0001-content-engine.adoc[ADR-0001].
27+
====
28+
29+
== Placeholders — `(:key)`
30+
31+
`(:key)` is replaced by the value of `key` in the context, rendered as an
32+
*accessible Shields.io badge* (the value as the badge, `key` as the label, with
33+
the FlexiText alt-text). A missing key renders `(:MISSING:key)` so gaps are
34+
visible, never silent.
35+
36+
[source,markdown]
37+
----
38+
Project phase: (:phase)
39+
Health: (:health-score)
40+
----
41+
42+
=== Filters
43+
44+
Chain filters with `|`:
45+
46+
[source,markdown]
47+
----
48+
Downloads: (:downloads | thousands-separator)
49+
Name: (:name | uppercase)
50+
----
51+
52+
Built-in filters: `thousands-separator`, `relativeTime`, `uppercase`,
53+
`lowercase`, `capitalize`, `round`. An unknown filter passes the value through
54+
unchanged.
55+
56+
== Conditionals & loops (DAX)
57+
58+
DAX adds block logic, evaluated before placeholders.
59+
60+
[source,markdown]
61+
----
62+
{{#if phase == production}}
63+
**Stable.** Safe to depend on.
64+
{{/if}}
65+
66+
{{#if status != archived}}
67+
Actively maintained.
68+
{{/if}}
69+
----
70+
71+
Loops iterate a comma-separated list value; inside the body, `(:var)` refers to
72+
the current item:
73+
74+
[source,markdown]
75+
----
76+
{{#for tag in tags}}
77+
- (:tag)
78+
{{/for}}
79+
----
80+
81+
Supported comparisons are `==` and `!=`; an unparseable condition renders
82+
nothing.
83+
84+
== Why FlexiText
85+
86+
Every Gnosis value is a `FlexiText` — a visual paired with alt-text whose smart
87+
constructor *rejects empty alt-text* (a `Left`, the "Tri-Guard" rule). That is
88+
why placeholder badges always carry an accessible description: it is impossible
89+
to construct one that does not. See link:accessibility.adoc[Accessibility].

0 commit comments

Comments
 (0)