§22 S6: type-driven entry noun (§22.4a) — backend source of truth + chrome

The displayed noun for an entry is a framework concept keyed on the collection's
immutable type — document→"RFC", specification→"Spec", bdd→"Feature" — not
deployment content. The chrome reads it from the API instead of hardcoding "RFC".

- collections.ENTRY_NOUN + entry_noun(type) (unknown type → generic "RFC").
- Surfaced as entry_noun on get_collection, list_collections items, the
  /api/deployment directory items, and GET /api/projects/:id.
- Frontend: Catalog reads entry_noun for the "+ Propose New <noun>" control;
  ProposeModal fetches the active collection's noun for its title + field copy.
- test_s6_entry_noun_vertical.py: map + API surfacing (collection + directory).

Scope note: this is §22.4a item (2) — terminology. The deeper type-module work
(item 1 per-type frontmatter schemas; item 3 specification release-planning +
bdd scenario/coverage surfaces) is flagged OPEN in the design doc and is handed
off to a follow-up spec+slice.

Backend 528 passed; frontend 30 passed + build green. Part of v0.45.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-06 01:35:57 -07:00
parent 79a27a946b
commit 839404da0c
5 changed files with 104 additions and 10 deletions
+6
View File
@@ -78,6 +78,11 @@ def make_router(config: Config, gitea: Gitea, bot: Bot) -> APIRouter:
"type": collections_mod.collection_type(
collections_mod.default_collection_id(r["id"])
),
"entry_noun": collections_mod.entry_noun(
collections_mod.collection_type(
collections_mod.default_collection_id(r["id"])
)
),
"visibility": r["visibility"],
}
for r in rows
@@ -221,6 +226,7 @@ def make_router(config: Config, gitea: Gitea, bot: Bot) -> APIRouter:
"name": row["name"],
"tagline": (dep["tagline"] if dep else "") or "",
"type": collections_mod.collection_type(cid),
"entry_noun": collections_mod.entry_noun(collections_mod.collection_type(cid)),
"visibility": row["visibility"],
"initial_state": collections_mod.collection_initial_state(cid),
"theme": cfg.get("theme") or {},
+21 -1
View File
@@ -14,6 +14,23 @@ from . import db
DEFAULT_COLLECTION_ID = "default"
# §22.4a item (2): the displayed noun for an entry is a type-driven label, a
# framework concept (like role names), not deployment content. The chrome reads
# this from the API rather than hardcoding "RFC", so a `specification` collection
# says "Spec" and a `bdd` collection says "Feature" with no per-deployment config.
ENTRY_NOUN = {
"document": "RFC",
"specification": "Spec",
"bdd": "Feature",
}
_DEFAULT_ENTRY_NOUN = "RFC"
def entry_noun(collection_type: str) -> str:
"""The §22.4a entry noun for a collection type. Unknown types fall back to
the generic 'RFC' so a future type is never label-less."""
return ENTRY_NOUN.get(collection_type, _DEFAULT_ENTRY_NOUN)
def _enabled_models_from_config(config_json: str | None) -> list[str] | None:
"""§22.12 per-collection enabled_models from a `config_json` blob, or None
@@ -86,6 +103,7 @@ def get_collection(collection_id: str) -> dict | None:
return None
out = dict(row)
out["enabled_models"] = _enabled_models_from_config(out.pop("config_json", None))
out["entry_noun"] = entry_noun(out["type"])
return out
@@ -102,5 +120,7 @@ def list_collections(project_id: str, include_unlisted: bool = False) -> list[di
for r in rows:
if not include_unlisted and r["visibility"] == "unlisted":
continue
out.append(dict(r))
item = dict(r)
item["entry_noun"] = entry_noun(item["type"])
out.append(item)
return out