import { bootstrap } from "./bootstrap.js"; import type { Hash, Store } from "./types.js"; // ── Text templates ────────────────────────────────────────────────── const TEXT_TEMPLATES: ReadonlyArray< readonly [alias: string, template: string] > = [ ["@ocas/output/put", "{{ payload }}"], [ "@ocas/output/get", "type: {{ payload.type }}\ntimestamp: {{ payload.timestamp }}", ], ["@ocas/output/has", "{{ payload }}"], ["@ocas/output/hash", "{{ payload }}"], ["@ocas/output/verify", "{{ payload }}"], ["@ocas/output/refs", "{% for ref in payload %}{{ ref }}\n{% endfor %}"], ["@ocas/output/walk", "{% for item in payload %}{{ item }}\n{% endfor %}"], ["@ocas/output/list", "{% for item in payload %}{{ item }}\n{% endfor %}"], [ "@ocas/output/list-meta", "{% for item in payload %}{{ item.hash }} {{ item.created }} {{ item.updated }}\n{% endfor %}", ], [ "@ocas/output/list-schema", "{% for item in payload %}{{ item.hash }} {{ item.created }} {{ item.updated }}\n{% endfor %}", ], [ "@ocas/output/var-set", "name: {{ payload.name }}\nschema: {{ payload.schema }}\nvalue: {{ payload.value }}", ], [ "@ocas/output/var-get", "name: {{ payload.name }}\nschema: {{ payload.schema }}\nvalue: {{ payload.value }}", ], [ "@ocas/output/var-delete", "name: {{ payload.name }}\nschema: {{ payload.schema }}\nvalue: {{ payload.value }}", ], [ "@ocas/output/var-list", "{% for v in payload %}name: {{ v.name }}\nschema: {{ v.schema }}\nvalue: {{ v.value }}\n{% endfor %}", ], [ "@ocas/output/tag", "{% for t in payload %}{{ t.key }}{% if t.value %}:{{ t.value }}{% endif %}\n{% endfor %}", ], [ "@ocas/output/untag", "{% for t in payload %}{{ t.key }}{% if t.value %}:{{ t.value }}{% endif %}\n{% endfor %}", ], [ "@ocas/output/var-history", "name: {{ payload.name }}\nschema: {{ payload.schema }}\n{% for v in payload.values %}{{ forloop.index0 }}: {{ v }}\n{% endfor %}", ], [ "@ocas/output/template-set", "schemaHash: {{ payload.schemaHash }}\ncontentHash: {{ payload.contentHash }}", ], ["@ocas/output/template-get", "{{ payload }}"], [ "@ocas/output/template-list", "{% for t in payload %}schemaHash: {{ t.schemaHash }}\ncontentHash: {{ t.contentHash }}\n{% endfor %}", ], ["@ocas/output/template-delete", "deleted: {{ payload.deleted }}"], [ "@ocas/output/gc", "total: {{ payload.total }}\nreachable: {{ payload.reachable }}\ncollected: {{ payload.collected }}\nscanned: {{ payload.scanned }}", ], [ "@ocas/output/export", "nodes: {{ payload.nodes }}\nvars: {{ payload.vars }}\ntags: {{ payload.tags }}", ], [ "@ocas/output/import", "nodes.imported: {{ payload.nodes.imported }}\nnodes.skipped: {{ payload.nodes.skipped }}\nvars.created: {{ payload.vars.created }}\nvars.updated: {{ payload.vars.updated }}\ntags: {{ payload.tags }}", ], ]; // ── HTML templates ────────────────────────────────────────────────── const HTML_TEMPLATES: ReadonlyArray< readonly [alias: string, template: string] > = [ // Simple value templates (card layout) [ "@ocas/output/put", '
' + '
Stored
' + '
{{ payload }}
' + "
", ], [ "@ocas/output/has", '
' + '
Exists
' + '
' + '{% if payload %}✓ yes' + '{% else %}✗ no{% endif %}' + "
", ], [ "@ocas/output/hash", '
' + '
Hash
' + '
{{ payload }}
' + "
", ], [ "@ocas/output/verify", '
' + '
Verify
' + '
' + '{% if payload == "ok" %}✓ ok' + '{% elsif payload == "corrupted" %}✗ corrupted' + '{% else %}⚠ invalid{% endif %}' + "
", ], [ "@ocas/output/template-get", '
' + '
Template
' + '
{{ payload }}
' + "
", ], [ "@ocas/output/template-delete", '
' + '
Template Deleted
' + '
' + '{% if payload.deleted %}✓ deleted' + '{% else %}✗ not found{% endif %}' + "
", ], // Structured templates (card layout with key-value grid) [ "@ocas/output/get", '
' + '
Node Detail
' + '
' + '
Type
{{ payload.type }}
' + "
Timestamp
{{ payload.timestamp }}
" + "{% if payload.tags %}" + "
Tags
{% for t in payload.tags %}" + '{{ t.key }}{% if t.value %}:{{ t.value }}{% endif %}' + "{% endfor %}
" + "{% endif %}" + "
" + "
", ], [ "@ocas/output/var-set", '
' + '
Variable Set
' + '
' + "
Name
{{ payload.name }}
" + '
Schema
{{ payload.schema }}
' + '
Value
{{ payload.value }}
' + "
" + "
", ], [ "@ocas/output/var-get", '
' + '
Variable Detail
' + '
' + "
Name
{{ payload.name }}
" + '
Schema
{{ payload.schema }}
' + '
Value
{{ payload.value }}
' + "{% if payload.valueTags %}" + "
Tags
{% for t in payload.valueTags %}" + '{{ t.key }}{% if t.value %}:{{ t.value }}{% endif %}' + "{% endfor %}
" + "{% endif %}" + "
" + "
", ], [ "@ocas/output/var-delete", '
' + '
Variable Deleted
' + '
' + "
Name
{{ payload.name }}
" + '
Schema
{{ payload.schema }}
' + '
Value
{{ payload.value }}
' + "
" + "
", ], [ "@ocas/output/template-set", '
' + '
Template Set
' + '
' + '
Schema
{{ payload.schemaHash }}
' + '
Content
{{ payload.contentHash }}
' + "
" + "
", ], // List/Array templates [ "@ocas/output/refs", '
' + '
References · {{ payload.size }} entries
' + '
", ], [ "@ocas/output/walk", '
' + '
Walk · {{ payload.size }} entries
' + '
", ], [ "@ocas/output/list", '
' + '
Nodes · {{ payload.size }} entries
' + '' + "{% for item in payload %}" + '' + '' + '' + "{% endfor %}
HASHCREATEDUPDATED
{{ item.hash }}{{ item.created }}{{ item.updated }}
", ], [ "@ocas/output/list-meta", '
' + '
Meta-Schemas · {{ payload.size }} entries
' + '' + "{% for item in payload %}" + '' + '' + '' + "{% endfor %}
HASHCREATEDUPDATED
{{ item.hash }}{{ item.created }}{{ item.updated }}
", ], [ "@ocas/output/list-schema", '
' + '
Schemas · {{ payload.size }} entries
' + '' + "{% for item in payload %}" + '' + '' + '' + "{% endfor %}
HASHCREATEDUPDATED
{{ item.hash }}{{ item.created }}{{ item.updated }}
", ], [ "@ocas/output/var-list", '
' + '
Variables · {{ payload.size }} entries
' + '' + "{% for v in payload %}" + '' + '' + '' + "{% endfor %}
NAMESCHEMAVALUE
{{ v.name }}{{ v.schema }}{{ v.value }}
", ], [ "@ocas/output/var-history", '
' + '
Variable History
' + '
' + "
Name
{{ payload.name }}
" + '
Schema
{{ payload.schema }}
' + "
" + '
    ' + "{% for v in payload.values %}
  1. " + '{{ v }}' + '{% if forloop.first %} ← current{% endif %}' + "
  2. {% endfor %}
" + "
", ], [ "@ocas/output/template-list", '
' + '
Templates · {{ payload.size }} entries
' + '' + "{% for t in payload %}" + '' + '' + "{% endfor %}
SCHEMACONTENT
{{ t.schemaHash }}{{ t.contentHash }}
", ], [ "@ocas/output/tag", '
' + '
Tags · {{ payload.size }} entries
' + '' + "{% for t in payload %}" + "" + '' + '' + "{% endfor %}
KEYVALUETARGET
{{ t.key }}{% if t.value %}{{ t.value }}{% else %}{% endif %}{{ t.target }}
", ], [ "@ocas/output/untag", '
' + '
Untagged · {{ payload.size }} entries
' + '' + "{% for t in payload %}" + "" + '' + '' + "{% endfor %}
KEYVALUETARGET
{{ t.key }}{% if t.value %}{{ t.value }}{% else %}{% endif %}{{ t.target }}
", ], // Statistics templates (card + stats-grid) [ "@ocas/output/gc", '
' + '
Garbage Collection
' + '
' + '
{{ payload.total }}' + 'total nodes
' + '
{{ payload.reachable }}' + 'reachable
' + '
' + '{% if payload.collected == 0 %}{{ payload.collected }}' + '{% else %}{{ payload.collected }}{% endif %}' + 'collected
' + '
{{ payload.scanned }}' + 'scanned
' + "
", ], [ "@ocas/output/export", '
' + '
Export Summary
' + '
' + '
{{ payload.nodes }}' + 'nodes
' + '
{{ payload.vars }}' + 'variables
' + '
{{ payload.tags }}' + 'tags
' + "
", ], [ "@ocas/output/import", '
' + '
Import Summary
' + '
' + '
{{ payload.nodes.imported }}' + 'nodes imported
' + '
{{ payload.nodes.skipped }}' + 'nodes skipped
' + '
{{ payload.vars.created }}' + 'variables created
' + '
{{ payload.vars.updated }}' + 'variables updated
' + '
{{ payload.tags }}' + 'tags
' + "
", ], ]; // ── Shared CSS ────────────────────────────────────────────────────── /** * Shared CSS for output HTML templates. * Uses `.ocas-` scoped class names and CSS custom properties per the design guide. */ const OUTPUT_CSS = [ // Design tokens ":root {" + " --ocas-font: system-ui, -apple-system, 'Segoe UI', sans-serif;" + " --ocas-mono: ui-monospace, 'SF Mono', 'Cascadia Code', monospace;" + " --ocas-bg: #fafafa;" + " --ocas-card-bg: #fff;" + " --ocas-card-border: #e5e7eb;" + " --ocas-card-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);" + " --ocas-card-radius: 8px;" + " --ocas-text: #1f2937;" + " --ocas-text-muted: #6b7280;" + " --ocas-green: #16a34a;" + " --ocas-red: #dc2626;" + " --ocas-yellow: #d97706;" + " --ocas-hash-bg: #f3f4f6;" + " --ocas-hash-text: #374151;" + " --ocas-metric-size: 1.75rem;" + " }", // Card container ".ocas-card { background: var(--ocas-card-bg); border: 1px solid var(--ocas-card-border);" + " box-shadow: var(--ocas-card-shadow); border-radius: var(--ocas-card-radius);" + " font-family: var(--ocas-font); color: var(--ocas-text); line-height: 1.5; max-width: 48rem; }", ".ocas-card-header { font-weight: 600; padding: 0.75rem 1rem;" + " border-bottom: 1px solid var(--ocas-card-border); }", ".ocas-card-body { padding: 1rem; }", // Hash pill ".ocas-hash { font-family: var(--ocas-mono); font-size: 0.9em;" + " background: var(--ocas-hash-bg); color: var(--ocas-hash-text);" + " padding: 0.15em 0.4em; border-radius: 4px; word-break: break-all; }", // Key-value grid ".ocas-dl { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; margin: 0; padding: 1rem; }", ".ocas-dl dt { font-weight: 600; color: var(--ocas-text-muted); }", ".ocas-dl dd { margin: 0; }", // Tag pills ".ocas-tag { display: inline-block; background: #eff6ff; color: #2563eb;" + " font-size: 0.85em; padding: 0.1em 0.5em; border-radius: 9999px; margin-right: 0.25rem; }", // Status badges ".ocas-badge { display: inline-block; padding: 0.25em 0.75em; border-radius: 9999px;" + " font-weight: 500; font-size: 0.9em; }", ".ocas-badge-ok { background: #dcfce7; color: var(--ocas-green); }", ".ocas-badge-error { background: #fee2e2; color: var(--ocas-red); }", ".ocas-badge-warn { background: #fef3c7; color: var(--ocas-yellow); }", // Template content code block ".ocas-template-content { white-space: pre-wrap; background: var(--ocas-hash-bg);" + " font-family: var(--ocas-mono); padding: 0.75rem; border-radius: 4px;" + " overflow-x: auto; margin: 0; }", // Stats grid (gc, export, import) ".ocas-stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; }", ".ocas-stat { display: flex; flex-direction: column; }", ".ocas-stat-value { font-size: var(--ocas-metric-size); font-weight: 600;" + " font-variant-numeric: tabular-nums; }", ".ocas-stat-label { color: var(--ocas-text-muted); font-size: 0.85em; }", // Semantic highlights ".ocas-success { color: var(--ocas-green); }", ".ocas-zero { color: var(--ocas-text-muted); }", // Hash list (refs, walk) ".ocas-hash-list { list-style: none; padding: 0; margin: 0; }", ".ocas-hash-list li { padding: 0.25rem 0; }", // History (var-history) ".ocas-history-values { padding-left: 1.5rem; margin: 0; }", ".ocas-history-values li { padding: 0.25rem 0; }", ".ocas-current-marker { color: var(--ocas-green); font-size: 0.85em; font-weight: 500; }", // Legacy .ocas-output support (structured/list/stats templates) ".ocas-output { font-family: var(--ocas-font); line-height: 1.5; max-width: 48rem; }", ".ocas-output dl { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; margin: 0; }", ".ocas-output dt { font-weight: 600; color: var(--ocas-text-muted); }", ".ocas-output dd { margin: 0; }", ".ocas-output table { border-collapse: collapse; width: 100%; }", ".ocas-output th, .ocas-output td { text-align: left; padding: 0.4rem 0.75rem; border-bottom: 1px solid var(--ocas-card-border); }", ".ocas-output th { font-weight: 600; color: var(--ocas-text-muted); background: #f9fafb; }", ".ocas-output ul { list-style: none; padding: 0; margin: 0; }", ".ocas-output li { padding: 0.2rem 0; }", ".ocas-stats .ocas-metric { font-size: 1.25em; font-weight: 600; }", ".ocas-verify-ok .ocas-status { color: var(--ocas-green); }", ".ocas-verify-corrupted .ocas-status { color: var(--ocas-red); }", ".ocas-verify-invalid .ocas-status { color: var(--ocas-yellow); }", // Table styles (design-guide-compliant) ".ocas-table { border-collapse: collapse; width: 100%; }", ".ocas-table th { text-align: left; padding: 0.4rem 0.75rem;" + " text-transform: uppercase; font-size: 0.75em; font-weight: 600;" + " color: var(--ocas-text-muted); letter-spacing: 0.05em;" + " border-bottom: 1px solid var(--ocas-card-border); }", ".ocas-table td { text-align: left; padding: 0.4rem 0.75rem;" + " border-bottom: 1px solid var(--ocas-card-border); }", ".ocas-table tr:last-child td { border-bottom: none; }", ".ocas-col-name { font-weight: 500; }", ".ocas-col-time { color: var(--ocas-text-muted); font-variant-numeric: tabular-nums; }", ].join(" "); /** * Register default LiquidJS templates for all @ocas/output/* schemas. * Each template is stored as a @ocas/string CAS node and bound to * the variable `@ocas/template/{format}/`. * * Registers both text and HTML templates. HTML templates also get a * shared CSS static template (`@ocas/template-static/html/`). * * Idempotent: safe to call multiple times. */ export async function registerOutputTemplates( store: Store, ): Promise> { const aliases = bootstrap(store); const stringHash = aliases["@ocas/string"]; if (stringHash === undefined) { throw new Error("@ocas/string schema not found in bootstrap result"); } const registered: Record = {}; // Register text templates for (const [alias, template] of TEXT_TEMPLATES) { const schemaHash = aliases[alias]; if (schemaHash === undefined) { throw new Error(`Schema alias not found: ${alias}`); } const contentHash = store.cas.put(stringHash, template); const varName = `@ocas/template/text/${schemaHash}`; store.var.set(varName, contentHash); registered[alias] = contentHash; } // Register HTML templates for (const [alias, template] of HTML_TEMPLATES) { const schemaHash = aliases[alias]; if (schemaHash === undefined) { throw new Error(`Schema alias not found: ${alias}`); } const contentHash = store.cas.put(stringHash, template); const varName = `@ocas/template/html/${schemaHash}`; store.var.set(varName, contentHash); // Register shared CSS static template for this schema const staticJson = JSON.stringify({ css: OUTPUT_CSS }); const staticHash = store.cas.put(stringHash, staticJson); const staticVarName = `@ocas/template-static/html/${schemaHash}`; store.var.set(staticVarName, staticHash); } return registered; }