feat: Template-Spec v1 Phase C — IF-Renderer + HOSTING/FEATURES + 4 neue DE-Templates
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 18s

- contextBridge.ts: HostingCtx + FeaturesCtx (35 Felder), ~50 neue Platzhalter-Aliases
- ruleEngine.ts: buildBoolContext() + applyConditionalBlocks() (IF/IF_NOT/IF_ANY)
- ruleEngine.test.ts: 67 Tests (+18 für Phase C), alle grün
- page.tsx: IF-Renderer in Pipeline, HOSTING+FEATURES Formular-Sections, erweiterter SDK-Prefill
- scripts/apply_templates_023.py: 4 neue DE-Templates (Cookie v2, DSE, AGB, Impressum)
- migrations/023_new_templates_de.sql: Dokumentation + Verifikations-Query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-04 14:35:56 +01:00
parent 7f3bf93cd6
commit e0f7f2134e
6 changed files with 1208 additions and 10 deletions

View File

@@ -359,6 +359,83 @@ export function getDocType(templateType: string, language: string): string {
return DOC_TYPE_MAP[key] ?? `${templateType}_${language}`
}
// =============================================================================
// buildBoolContext: combines computed flags + derived + FEATURES booleans
// =============================================================================
export function buildBoolContext(ctx: TemplateContext, flags: ComputedFlags): Record<string, boolean> {
const f = ctx.FEATURES
return {
// --- From computed flags ---
IS_B2C: flags.IS_B2C,
IS_B2B: flags.IS_B2B,
SERVICE_IS_SAAS: flags.SERVICE_IS_SAAS,
SERVICE_IS_HYBRID: flags.SERVICE_IS_HYBRID,
HAS_PENALTY: flags.HAS_PENALTY,
HAS_ANALYTICS: flags.HAS_ANALYTICS,
ANALYTICS_ENABLED: flags.HAS_ANALYTICS, // alias used in cookie banner template
HAS_MARKETING: flags.HAS_MARKETING,
MARKETING_ENABLED: flags.HAS_MARKETING, // alias
// --- Derived from PROVIDER ---
HAS_REGISTER: !!(ctx.PROVIDER.REGISTER_COURT),
HAS_VAT_ID: !!(ctx.PROVIDER.VAT_ID),
CONTACT_PHONE: !!(ctx.PROVIDER.PHONE),
// --- Derived from PRIVACY ---
HAS_DPO: !!(ctx.PRIVACY.DPO_NAME),
// --- From FEATURES booleans ---
THIRD_COUNTRY_POSSIBLE: f.HAS_THIRD_COUNTRY,
HAS_THIRD_COUNTRY: f.HAS_THIRD_COUNTRY,
FUNCTIONAL_ENABLED: f.HAS_FUNCTIONAL_COOKIES,
HAS_FUNCTIONAL_COOKIES: f.HAS_FUNCTIONAL_COOKIES,
CMP_LOGS_CONSENTS: f.CMP_LOGS_CONSENTS,
HAS_NEWSLETTER: f.HAS_NEWSLETTER,
HAS_ACCOUNT: f.HAS_ACCOUNT,
HAS_PAYMENTS: f.HAS_PAYMENTS,
HAS_SUPPORT: f.HAS_SUPPORT,
HAS_SOCIAL_MEDIA: f.HAS_SOCIAL_MEDIA,
HAS_PAID_PLANS: f.HAS_PAID_PLANS,
HAS_SLA: f.HAS_SLA,
HAS_EXPORT_POLICY: f.HAS_EXPORT_POLICY,
HAS_WITHDRAWAL: f.HAS_WITHDRAWAL,
HAS_REGULATED_PROFESSION: f.HAS_REGULATED_PROFESSION,
HAS_EDITORIAL_RESPONSIBLE: f.HAS_EDITORIAL_RESPONSIBLE,
HAS_DISPUTE_RESOLUTION: f.HAS_DISPUTE_RESOLUTION,
}
}
// =============================================================================
// applyConditionalBlocks: processes {{#IF}}/{{#IF_NOT}}/{{#IF_ANY}} directives
// Runs BEFORE placeholder substitution.
// =============================================================================
export function applyConditionalBlocks(content: string, boolCtx: Record<string, boolean>): string {
let result = content
// {{#IF_NOT COND}}...{{/IF_NOT}} — process before IF to avoid conflicts
result = result.replace(
/\{\{#IF_NOT ([A-Z_]+)\}\}([\s\S]*?)\{\{\/IF_NOT\}\}/g,
(_, cond: string, body: string) => (boolCtx[cond] ? '' : body)
)
// {{#IF_ANY A B C}}...{{/IF_ANY}}
result = result.replace(
/\{\{#IF_ANY ([A-Z_ ]+)\}\}([\s\S]*?)\{\{\/IF_ANY\}\}/g,
(_, conds: string, body: string) =>
conds.trim().split(/\s+/).some((c) => boolCtx[c]) ? body : ''
)
// {{#IF COND}}...{{/IF}}
result = result.replace(
/\{\{#IF ([A-Z_]+)\}\}([\s\S]*?)\{\{\/IF\}\}/g,
(_, cond: string, body: string) => (boolCtx[cond] ? body : '')
)
return result
}
// =============================================================================
// applyBlockRemoval: removes [BLOCK:ID]…[/BLOCK:ID] markers from content
// =============================================================================