// 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= 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 a render pass, not by Scan. Links []string `json:"links,omitempty"` // WordCount is an approximate word count of the rendered text. Filled in by // a render pass, not by Scan. 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"` }