krz/orgo

Lightning fast org-mode static site generator.

clone: git clone https://gitbay.org/krz/orgo.git

ce3f2623d5c9eede9c2179aa21afcdf1a4fea05e

signed_unknown_key

author: Christian Cleberg <hello@cleberg.net> · 2026-08-23T04:39:33Z
committer: <noreply@github.com>

audit: special blocks are supported, not blind spots (#28)

`#+begin_note` was reported twice as a gap: `??? NOTE` in the block census and
`OUT unmodelled block type` in the construct table. Neither is true. Any block
name with no dedicated handling renders as a div carrying that name and holding
parsed org, which is exactly what org's HTML exporter emits for it — verified
against Emacs 30.2, and already covered by the oracle, since
`fixtures/blocks.org` carries a `#+BEGIN_NOTE` and reports 0 unexplained
divergences. `docs/guide/05-org-support.org:96` has documented this all along.

So no block name is out of scope and none is unrecognised. `scope_of_block` and
`KNOWN_BLOCKS` are gone with it.

`VERSE` and `COMMENT` were also mislabelled: both are handled (a comment block
is dropped from the page, as in org) but fell through to "unmodelled block
type". They now have their own names.

cleberg.net's corpus audits at 100.0% coverage, 0 out-of-scope.

Closes #25
 docs/guide/09-auditing.org | 14 +++++---------
 src/audit.rs               | 26 +++++++++-----------------
 tests/constructs.rs        | 21 +++++++++++++++++++++
 3 files changed, 35 insertions(+), 26 deletions(-)

diff --git a/docs/guide/09-auditing.org b/docs/guide/09-auditing.org
index eab289f..c3cb152 100644
--- a/docs/guide/09-auditing.org
+++ b/docs/guide/09-auditing.org
@@ -26,26 +26,22 @@ IN   heading                              1148     176  blog/2018-11-28-aes-encr
 IN   verbatim                             1048     130  blog/2018-11-28-aes-encryption.org:79
 ...
 IN   table formula (#+TBLFM:)                4       1  blog/2024-08-11-org-mode-features.org:191
-OUT  unmodelled block type                   1       1  blog/2026-03-03-auditing-aws-s3.org:50
+IN   special block                           1       1  blog/2026-03-03-auditing-aws-s3.org:50
 
-coverage: 9001 in-scope use(s) (100.0%), 1 out-of-scope (0.0%)
+coverage: 9002 in-scope use(s) (100.0%), 0 out-of-scope (0.0%)
 
 KEYWORDS
    SLUG                                  180     180  blog/2018-11-28-aes-encryption.org:4
    TITLE                                 180     180  blog/2018-11-28-aes-encryption.org:2
 ...
-
-BLOCK TYPES
-   SRC                                   941     123  blog/2018-11-28-cpp-compiler.org:17
-   QUOTE                                 115      48  blog/2018-11-28-aes-encryption.org:21
-??? NOTE                                    1       1  blog/2026-03-03-auditing-aws-s3.org:50
 #+END_EXAMPLE
 
 - =IN= is supported; =OUT= is excluded by design and degrades as described in
   [[file:05-org-support.org][Org support]].
 - The *coverage* line is the number to look at first.
-- =???= marks a name orgo does not recognise at all — here =#+begin_note=, which one
-  post uses once. That is the blind-spot signal: not "known unsupported", but unknown.
+- =???= marks a name orgo does not recognise at all. That is the blind-spot signal —
+  not "known unsupported", but unknown — and this corpus has none. Block names never
+  carry it: an unrecognised one is still a special block, and still renders.
 
 Four censuses follow the construct table: every distinct =#+KEYWORD:=, block type,
 drawer name and link scheme in the corpus. A =???= in any of them is worth a look.
diff --git a/src/audit.rs b/src/audit.rs
index db6202b..e0a3b0d 100644
--- a/src/audit.rs
+++ b/src/audit.rs
@@ -83,12 +83,6 @@ const KNOWN_KEYWORDS: &[&str] = &[
     // Leaving them out reported the corpus's most-used keyword as unrecognized.
     "SLUG", "DRAFT", "TEMPLATE",
 ];
-/// Blocks with dedicated handling. Any *other* name renders as a special block — a div
-/// with that name holding parsed org — so an unlisted block is a note about what a corpus
-/// contains rather than a construct that will be lost.
-const KNOWN_BLOCKS: &[&str] = &[
-    "SRC", "QUOTE", "EXAMPLE", "CENTER", "EXPORT", "VERSE", "COMMENT",
-];
 const KNOWN_DRAWERS: &[&str] = &["PROPERTIES", "LOGBOOK", "END"];
 /// Keyword names conventional enough to be worth flagging when they lead a heading.
 /// A custom sequence is only *real* if some `#+TODO:` declares it, which the census
@@ -107,7 +101,11 @@ impl Audit {
     pub fn is_known(kind: Census, name: &str) -> bool {
         let known = match kind {
             Census::Keyword => KNOWN_KEYWORDS,
-            Census::Block => KNOWN_BLOCKS,
+            // Every block name renders, and renders as org renders it: the names in
+            // `block_construct` through dedicated handling, every other name as a special
+            // block — a div carrying the name, holding parsed org, which is exactly what
+            // org's exporter emits. No block name is a blind spot.
+            Census::Block => return true,
             Census::Drawer => KNOWN_DRAWERS,
             Census::Scheme => KNOWN_SCHEMES,
         };
@@ -198,7 +196,7 @@ impl Audit {
             if let Some(rest) = trimmed.to_ascii_uppercase().strip_prefix("#+BEGIN_") {
                 let kind = rest.split_whitespace().next().unwrap_or("").to_string();
                 self.count_census(Census::Block, &kind, &at);
-                self.count(scope_of_block(&kind), block_construct(&kind), &at);
+                self.count(Scope::In, block_construct(&kind), &at);
                 if trimmed.to_ascii_uppercase().contains(":RESULTS") {
                     self.count(Scope::Out, "babel header args (:results)", &at);
                 }
@@ -440,14 +438,6 @@ fn is_drawer(trimmed: &str) -> bool {
         && t.len() > 2
 }
 
-fn scope_of_block(kind: &str) -> Scope {
-    if KNOWN_BLOCKS.iter().any(|k| k.eq_ignore_ascii_case(kind)) {
-        Scope::In
-    } else {
-        Scope::Out
-    }
-}
-
 fn block_construct(kind: &str) -> &'static str {
     match kind.to_ascii_uppercase().as_str() {
         "SRC" => "source block",
@@ -455,7 +445,9 @@ fn block_construct(kind: &str) -> &'static str {
         "EXAMPLE" => "example block",
         "CENTER" => "center block",
         "EXPORT" => "export block",
-        _ => "unmodelled block type",
+        "VERSE" => "verse block",
+        "COMMENT" => "comment block",
+        _ => "special block",
     }
 }
 
diff --git a/tests/constructs.rs b/tests/constructs.rs
index 5a1e421..3ec0978 100644
--- a/tests/constructs.rs
+++ b/tests/constructs.rs
@@ -816,3 +816,24 @@ fn audit_counts_table_formulas_as_in_scope() {
         "a #+TBLFM: table orgo renders exactly as Emacs does must not read as a gap:\n{line}"
     );
 }
+
+/// A block name with no dedicated handling is a special block, which org itself renders
+/// as a div carrying the name — `fixtures/blocks.org` holds `#+BEGIN_NOTE` and is in the
+/// Emacs oracle. So it is neither out of scope nor a `???` blind spot.
+#[test]
+fn audit_counts_special_blocks_as_in_scope() {
+    let report = audit_fixture("blocks.org");
+    let line = report
+        .lines()
+        .find(|l| l.contains("special block"))
+        .expect("the NOTE block is counted as a special block");
+    assert!(
+        line.starts_with("IN "),
+        "a block orgo renders exactly as Emacs does must not read as a gap:\n{line}"
+    );
+    let flagged: Vec<&str> = report.lines().filter(|l| l.starts_with("???")).collect();
+    assert!(
+        flagged.is_empty(),
+        "no block name is unrecognised — every one renders: {flagged:?}"
+    );
+}