add the gallery shortcode and the seam it needed

A feature now learns which bundle is rendering: render.Bundle puts an Origin —
the bundle's directory plus the rooted fs.FS — on the parse context, and
render.OriginFrom reads it back. Available while parsing, not while rendering,
which decides where a feature does its filesystem work: goldmark hands the
context to a block parser and not to a node renderer, so gallery gathers its
filenames at parse time and carries them on the node.

Reads stay inside the site root because Origin passes the fs.FS rather than a
path to join (ADR-0031).

Fragment{Args, Items} lands with it (ADR-0037), so figure's template now reads
.Args.src. Authored arguments and engine-gathered items stay in separate fields:
a src argument beside a src the engine found would otherwise silently pick one.

A gallery is pictures beside the bundle, in filename order, skipping
subdirectories and anything a browser cannot show. Filename order is what makes
the sparse numeric-prefix convention work without numbers in URLs (ADR-0016).

New latent row: the reference theme's images carry no width/height and a
gallery's carry no alt, which is below the output floor conventions.md states.
Nothing can supply either yet — dimensions need the image read, and a filename is
not alt text. Queue 13 computes dimensions and brings structured items with it.
This commit is contained in:
2026-07-30 10:39:37 +06:00
parent 0bd04df38e
commit 61ae5c9c14
8 changed files with 198 additions and 28 deletions
+4 -3
View File
@@ -11,15 +11,15 @@ If this file disagrees with the code, the code is right and this file is a bug.
| `internal/content/doc.go` | package comment | 5 |
| `internal/content/content.go` | bundles: `os.Root` open, walk, frontmatter split, key/lang derivation, NFC, tag slugs, permalink building | 352 |
| `internal/content/site.go` | the indexed site: lookup with language fallback, aliases, `Query` and `Run`, sections, `Sequence` | 286 |
| `internal/render/render.go` | goldmark with the typographer, per-kind template sets with site override, the `Partial` seam features render through, `Page`/`List`/`Sequence`/`head` | 345 |
| `internal/render/render.go` | goldmark with the typographer, per-kind template sets with site override, the `Partial`/`Origin` seams features render and resolve through, `Page`/`List`/`Sequence`/`head` | 380 |
| `internal/render/chrome.go` | the engine's own words: phrase table, month names, digits, and the `t`/`num`/`day` template funcs (ADR-0034) | 105 |
| `internal/render/templates/` | reference theme: `base.html`, `page.html`, `list.html`, `shortcodes.html`, `theme.css` (ADR-0026) | — |
| `internal/ext/shortcodes/` | first feature: `{{< name key="value" >}}` block parser and node renderer, rendering through a theme fragment (ADR-0036) | 168 |
| `internal/ext/shortcodes/` | first feature: `{{< name key="value" >}}` block parser and node renderer, rendering through a theme fragment (ADR-0036). `figure`, `gallery` | 212 |
| `cmd/khosra/wire.go` | the only list of enabled features (`extensions.md`) | 19 |
| `internal/web/resolve.go` | URL → (key, lang, page, tag) or a canonical redirect: language prefix, `/en/…` fork guard, pagination, tags, trailing slash | 112 |
| `internal/web/web.go` | handler: resolve, look up with fallback, section and tag listings, sequence, `/static/` (misses and refusals alike answer 404), degrade on failure | 152 |
| `cmd/khosra/main.go` | flags, wiring, startup — the only place things are assembled | 53 |
| `*_test.go` | table-driven, one file per source file; symlink escape (content and static), canonical paths, language fallback, aliases, pagination, tags, sequences, chrome, typography, shortcode escaping, 404 | 1288 |
| `*_test.go` | table-driven, one file per source file; symlink escape (content and static), canonical paths, language fallback, aliases, pagination, tags, sequences, chrome, typography, shortcode escaping, galleries, 404 | 1363 |
Serves a bundle at `/{section}/{slug}/`, a paginated listing per section, tag listings global and
section-narrowed, sequence navigation and a series archive on any nested bundle, and `static/` verbatim.
@@ -63,6 +63,7 @@ with a stated reason. A list nothing drains is a graveyard of known defects.
| No mechanical check that the counters are *correct* | Accepted at the Arc 1 boundary: the coupling gate makes forgetting them impossible, which is the real failure mode, and checking the values needs code to count | The first page-level transform (queue 15), now that the transform counter means something narrower |
| No mechanical gate on the untrusted boundary (ADR-0003) | Scheduled to Arc 3: nothing untrusted is read yet. Half of it is now mechanical — `verify.sh` rejects `WithUnsafe`, so authored Markdown cannot become HTML — but there is still no check that a *future* untrusted source stays out of shortcode and template evaluation | The comment path — a test that untrusted input reaches no shortcode or template evaluation |
| `date` stays in `Extra` after being lifted onto `Bundle.Date`, unlike `title`, `aliases`, `tags` and `order`, which are deleted | Spotted while adding `order`; the theme contract says `Extra` holds what the parser does not name, so one of the two is wrong. Harmless today — a template reading `.Extra.date` gets the raw YAML value | Whatever next reads `Extra` generically: feeds (queue 14) or `check` (17) |
| The reference theme's images carry no `width`/`height`, and a gallery's carry no `alt` — below the output floor `conventions.md` states | Nothing can supply them yet: dimensions need the image read, and a filename is not alt text. An empty `alt` is at least honest about a picture nothing describes | Image derivatives (queue 13) compute dimensions; structured gallery items with captions land with them (ADR-0037's revisit note) |
| Sequence resolution rescans the index on every bundle request — two passes over every key, each doing a `Lookup` | No cache exists anywhere yet, and a site of this size resolves in microseconds. Measuring first is the rule (queue 16) | The page cache (queue 16), which is the thing that makes the cost visible |
## Open questions