// Package doc is spec.sr.ht's read model for a space's documents: it turns the
// markdown blobs of one git revision into an addressable Archive, resolves the
// [[wikilinks]] between them, and renders them to HTML.
//
// It is warren's vault/ + render/ packages absorbed, with exactly one
// structural change and one consolidation.
//
// # No filesystem
//
// warren scanned a directory: filepath.WalkDir plus os.ReadFile. There is no
// checkout here, so Scan walks a git tree instead — every read resolves a
// revision and reads blobs, which is what makes the approved head, a pinned
// ?rev=<sha> and a proposal branch the same code path with a different rev.
// The seam warren already had, FromPages, is untouched in spirit: it builds an
// Archive out of a page set with no I/O at all, and everything downstream of
// Archive — resolution, backlinks, hierarchy, rendering — is unaware of where
// the pages came from.
//
// # One frontmatter parser
//
// warren's vault/frontmatter.go had its own YAML header parser. core/ already
// owns that contract (core.SplitFrontmatter, core.ParseFrontmatter,
// core.Frontmatter, core.Schema, core.DocID) and it is the one the write plane
// and the push hook validate against. Front therefore embeds core.Frontmatter
// and adds only what core deliberately does not model — `parent:`, `aliases:`,
// `planned:`, and the ordered key list used for display and search text. Two
// parsers that disagree about a document header is a bug that surfaces months
// later, in the registry, not at the door.
//
// # Tolerant on the read path, strict at the door
//
// core's parser is strict, because a malformed header must be rejected when it
// is proposed or pushed. This package is not the door: `--push-option=
// skip-validation` exists, so a document with a broken header can be on the
// approved branch, and refusing to render it would turn a cosmetic typo into an
// outage. A header this package cannot parse degrades to "no frontmatter" — the
// document still renders, still indexes, and still has a title from its first
// H1 or its file name.
package doc
import "sourcecraft.dev/bigbes/sr-ht-spec/core"
// PageKind distinguishes ordinary documents from the two structurally unusual
// kinds — hand-maintained catalogs and append-only logs — whose links are
// suppressed in backlink counts.
type PageKind string
const (
KindMarkdown PageKind = "markdown"
// KindCatalog is a hand-maintained listing page that links to nearly every
// document in its section. Marked by frontmatter `type: catalog`, or by the
// file name `index.md` as a fallback.
KindCatalog PageKind = "catalog"
// KindLog is an append-only activity log carrying dated entries. Marked by
// frontmatter `type: log`, or by the file name `log.md` as a fallback.
KindLog PageKind = "log"
)
// Page is one document of a space at one revision, as the read plane addresses
// it. It carries no body: bodies live in git and are read by blob sha, which is
// what keeps an Archive cheap to build and impossible to serve stale.
type Page struct {
// ID is the archive's addressing key: the document's frontmatter id when it
// has a well-formed one that no other document in the space claims, and
// otherwise its path without the ".md" extension.
//
// Falling back to the path rather than to warren's bare filename stem is
// deliberate. warren keyed on the stem because Obsidian resolves wikilinks
// that way; here `id` is the load-bearing field and paths are unique by
// construction, so the fallback is unique too and cannot be taken away from
// a document by an unrelated file appearing elsewhere.
ID string `json:"id"`
// DocID is the frontmatter `id:` when it parses as a core.DocID, otherwise
// empty. A document with a malformed or duplicated id keeps its path and
// stays readable — it is excluded from id resolution, not from the archive.
DocID string `json:"doc_id,omitempty"`
Kind PageKind `json:"kind"`
Title string `json:"title"`
// Path is the document's path in the git tree, forward-slashed, with its
// ".md" extension.
Path string `json:"path"`
// Blob is the hex sha of the document's blob at this revision: the render
// cache key. Content-addressed, so an entry keyed by it can never go stale.
Blob string `json:"blob,omitempty"`
Status core.Status `json:"status,omitempty"`
Summary string `json:"summary,omitempty"`
Tags []string `json:"tags,omitempty"`
// ParentID is the resolved ID of the document named by `parent:`, or empty.
ParentID string `json:"parent_id,omitempty"`
// Section is the top-level directory the document lives under ("specs",
// "notes", "reports"), used to group results and to resolve a bare wikilink
// in favour of the linking document's own section.
Section string `json:"section"`
// Crumbs is the ordered list of ancestor IDs from root to this document's
// parent (not including the document itself).
Crumbs []string `json:"crumbs,omitempty"`
// Links holds the IDs of documents this one links to, deduplicated, in
// order of first appearance. Filled in by [Archive.LinkPass], not by Scan:
// links come out of a render, not out of a frontmatter parse.
Links []string `json:"links,omitempty"`
// WordCount is an approximate word count of the rendered text. Filled in by
// [Archive.LinkPass], from the same render.
WordCount int `json:"word_count,omitempty"`
}
// DocProperty is one frontmatter key flattened to text, in document order.
type DocProperty struct {
Name string `json:"name"`
Value string `json:"value"`
}