fix(browse,server): sync .zddc lint keys, viewable schema pill, accurate virtual-source text

Three .zddc previewer fixes reported against the browse YAML editor:

- Lint no longer flags valid keys. browse/js/preview-yaml.js TOP_KEYS had
  drifted from the Go decoder (zddc/internal/zddc/file.go): party_source,
  history, history_globs, records, auto_own_roles, received_path,
  planned_response_date, planned_review_date, field_codes were all reported
  as "unknown key". Add them with appropriate type tags plus an 'object'
  case in checkValue for the free-form maps (records, field_codes).

- The ".zddc schema" pill is now clickable (↗) — opens the canonical JSON
  Schema the lint mirrors at /.api/zddc-schema (no-auth, read-only).

- The synthetic virtual-.zddc header comment named an internal source path
  (internal/zddc/defaults/'s paths: tree) that an operator can't act on. It
  now names the operator-facing artifact: the built-in defaults bundle,
  exportable/overridable as a root .zddc.zip via `zddc-server show-defaults`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-07 11:00:02 -05:00
parent a0e467200e
commit 0d7feb3468
3 changed files with 56 additions and 13 deletions

View file

@ -29,6 +29,16 @@
border-color: var(--primary); border-color: var(--primary);
color: var(--primary); color: var(--primary);
} }
/* The ".zddc schema" badge is clickable — it opens the full JSON Schema. */
.yaml-shell__schema--link {
cursor: pointer;
}
.yaml-shell__schema--link:hover,
.yaml-shell__schema--link:focus-visible {
background: var(--primary);
color: var(--bg);
outline: none;
}
/* CodeMirror has to fill the grid cell. The vendored CSS sets /* CodeMirror has to fill the grid cell. The vendored CSS sets
`height: 300px` by default we override to 100% so it grows with `height: 300px` by default we override to 100% so it grows with

View file

@ -114,7 +114,18 @@
views: 'viewmap', views: 'viewmap',
convert: 'convert', convert: 'convert',
created_by: 'string', created_by: 'string',
inherit: 'bool' inherit: 'bool',
// Keys the Go decoder (zddc/internal/zddc/file.go) accepts that the
// lint was missing — flagged valid configs as "unknown key".
party_source: 'string',
history: 'bool',
history_globs: 'string[]',
records: 'object',
auto_own_roles: 'string[]',
received_path: 'string',
planned_response_date: 'string',
planned_review_date: 'string',
field_codes: 'object'
}; };
var ACL_KEYS = { inherit: 'bool', permissions: 'stringmap', var ACL_KEYS = { inherit: 'bool', permissions: 'stringmap',
@ -279,6 +290,12 @@
if (t !== 'object') { addTypeErr(path, kind, t, issues); return; } if (t !== 'object') { addTypeErr(path, kind, t, issues); return; }
walkObject(val, CONVERT_KEYS, path, issues); walkObject(val, CONVERT_KEYS, path, issues);
return; return;
case 'object':
// Free-form map (records, field_codes) — the server accepts any
// nested shape, so we only check it's a mapping, not its keys.
if (t === 'null') return;
if (t !== 'object') { addTypeErr(path, kind, t, issues); return; }
return;
} }
} }
@ -433,9 +450,21 @@
var schemaTag = document.createElement('span'); var schemaTag = document.createElement('span');
schemaTag.className = 'md-shell__source yaml-shell__schema'; schemaTag.className = 'md-shell__source yaml-shell__schema';
if (isZddcFile(node.name)) { if (isZddcFile(node.name)) {
schemaTag.textContent = '.zddc schema'; schemaTag.textContent = '.zddc schema';
schemaTag.title = 'Linted against the .zddc cascade schema ' schemaTag.title = 'Linted against the .zddc cascade schema '
+ '(unknown keys, bad enums, and wrong types are flagged).'; + '(unknown keys, bad enums, and wrong types are flagged). '
+ 'Click to view the full JSON Schema.';
// Clickable → opens the canonical machine grammar the lint mirrors.
schemaTag.classList.add('yaml-shell__schema--link');
schemaTag.setAttribute('role', 'link');
schemaTag.setAttribute('tabindex', '0');
var openSchema = function () {
window.open('/.api/zddc-schema', '_blank', 'noopener');
};
schemaTag.addEventListener('click', openSchema);
schemaTag.addEventListener('keydown', function (ev) {
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openSchema(); }
});
} else { } else {
schemaTag.textContent = 'YAML'; schemaTag.textContent = 'YAML';
} }

View file

@ -143,9 +143,10 @@ func ServeZddcFile(cfg config.Config, w http.ResponseWriter, r *http.Request) {
// renderVirtualZddc produces a YAML body for a directory that has no // renderVirtualZddc produces a YAML body for a directory that has no
// .zddc on disk. The body is the cascade's leaf-level ZddcFile — // .zddc on disk. The body is the cascade's leaf-level ZddcFile —
// i.e. what internal/zddc/defaults/'s paths: tree declares for this exact // i.e. what the built-in defaults bundle (exportable/overridable as a
// directory, plus any contributions the walker threaded through. The // root .zddc.zip via `zddc-server show-defaults`) declares for this
// goal is to expose the embedded defaults' source of truth: a new // exact directory, plus any contributions the walker threaded through.
// The goal is to expose the baseline's source of truth: a new
// user opening the virtual .zddc here sees, in the same yaml shape // user opening the virtual .zddc here sees, in the same yaml shape
// they would write themselves, what behavior is currently declared // they would write themselves, what behavior is currently declared
// at this level. A header comment names the source and points at // at this level. A header comment names the source and points at
@ -163,13 +164,16 @@ func renderVirtualZddc(chain zddc.PolicyChain) (string, error) {
var b strings.Builder var b strings.Builder
b.WriteString("# Virtual .zddc — no file on disk at this directory.\n") b.WriteString("# Virtual .zddc — no file on disk at this directory.\n")
b.WriteString("# The content below is what the embedded defaults\n") b.WriteString("# The content below is what the policy baseline declares\n")
b.WriteString("# (internal/zddc/defaults/'s paths: tree) declare for this\n") b.WriteString("# for this exact path: the built-in defaults bundle — the\n")
b.WriteString("# exact path. Edit and save through the YAML editor in\n") b.WriteString("# same one you can export, and override, as a root\n")
b.WriteString("# browse to materialise a real .zddc here carrying your\n") b.WriteString("# .zddc.zip (`zddc-server show-defaults`) — with any\n")
b.WriteString("# changes; the bytes you save become the override\n") b.WriteString("# on-disk ancestor .zddc overrides already threaded in.\n")
b.WriteString("# verbatim (no merge, no synthesis — .zddc files drive\n") b.WriteString("# Edit and save through the YAML editor in browse to\n")
b.WriteString("# policy and are the single source of truth).\n") b.WriteString("# materialise a real .zddc here carrying your changes;\n")
b.WriteString("# the bytes you save become the override verbatim (no\n")
b.WriteString("# merge, no synthesis — .zddc files drive policy and are\n")
b.WriteString("# the single source of truth).\n")
b.WriteString("#\n") b.WriteString("#\n")
b.WriteString("# For the COMPOSED effective config across the whole\n") b.WriteString("# For the COMPOSED effective config across the whole\n")
b.WriteString("# cascade (all ancestors merged), query:\n") b.WriteString("# cascade (all ancestors merged), query:\n")