@warpgogol/werkstatt-shared 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -69
- package/src/integration/funnel.ts +78 -1
- package/src/integration/index.ts +19 -2
- package/src/integration/lagebild-ingress.ts +169 -0
- package/src/integration/orchestration.ts +7 -13
- package/src/integration/port-barrel.ts +9 -28
- package/src/integration/tests/funnel-bridge.test.ts +7 -7
- package/src/integration/tests/lagebild-ingress.test.ts +118 -0
- package/src/observability/typed-refs.ts +1 -1
- package/src/ontology/analytics/matomo-fleet.registry.yaml +1 -1
- package/src/ontology/archetypes/index.json +12 -9
- package/src/ontology/archetypes/index.yaml +26 -10
- package/src/ontology/blueprints/website-local.yaml +11 -11
- package/src/ontology/blueprints/website-service.yaml +20 -20
- package/src/ontology/capabilities/lead.submit.yaml +2 -2
- package/src/ontology/enums.ts +2 -2
- package/src/ontology/external-surfaces/jsonld-types.yaml +14 -14
- package/src/ontology/external-surfaces/url-schema.yaml +1 -1
- package/src/ontology/index.ts +2 -0
- package/src/ontology/schemas/index.ts +7 -2
- package/src/ontology/schemas/system/integrations.ts +1 -1
- package/src/ontology/schemas/system/manifest.ts +38 -1
- package/src/ontology/schemas/system.ts +7 -2
- package/src/ontology/shared-section-props/common.ts +11 -0
- package/src/ontology/shared-section-props/visual-header.ts +1 -1
- package/src/share/agent/api-catalog.ts +5 -0
- package/src/share/agent/ard-catalog.ts +14 -0
- package/src/share/agent/index.ts +2 -0
- package/src/share/agent/manifest.ts +25 -2
- package/src/share/agent/mcp-card.ts +4 -0
- package/src/share/agent/openapi.ts +78 -0
- package/src/share/agent/search.test.ts +188 -0
- package/src/share/agent/search.ts +164 -0
- package/src/share/env.d.ts +0 -4
- package/src/share/schemas/effects.ts +16 -0
- package/src/share/schemas/media.ts +4 -0
- package/src/share/schemas/section-shell.ts +2 -1
- package/src/share/scripts/external-link-qr.ts +9 -1
- package/src/share/scripts/gsap-counter.ts +2 -0
- package/src/share/scripts/inline-number-animation.ts +2 -0
- package/src/share/scripts/live-photos.ts +2 -0
- package/src/share/scripts/video-player.test.ts +210 -0
- package/src/share/scripts/video-player.ts +73 -13
- package/src/share/semantic/extract.ts +25 -1
- package/src/share/semantic/tests/split-sentences.test.ts +57 -0
- package/src/share/tests/manifest.test.ts +51 -0
- package/src/share/tests/openapi.test.ts +30 -0
- package/src/share/types/qrcode-browser.d.ts +18 -0
- package/src/share/utility-registry.yaml +2 -2
- package/src/integration/crm-buffer.ts +0 -491
- package/src/integration-adapter-supabase-crm/adapter.ts +0 -437
- package/src/integration-adapter-supabase-crm/client.ts +0 -605
- package/src/integration-adapter-supabase-crm/index.ts +0 -28
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +0 -482
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +0 -89
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +0 -334
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +0 -83
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +0 -409
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +0 -288
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +0 -80
- package/src/integration-adapter-supabase-crm/vitest.config.ts +0 -8
- package/src/integration-adapter-supabase-crm/worker.ts +0 -223
- package/src/ontology/archetypes/components/currency-selector.yaml +0 -19
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for the Lagebild ingress HTTP client. Verifies payload
|
|
4
|
+
construction, idempotency key generation, and network error handling.</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not make real HTTP calls — fetch is mocked.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { test, expect, vi } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
submitIngress,
|
|
14
|
+
buildIdempotencyKey,
|
|
15
|
+
LAGEBILD_INGRESS_CONTRACT_VERSION,
|
|
16
|
+
type LagebildIngressConfig,
|
|
17
|
+
} from "../lagebild-ingress.ts";
|
|
18
|
+
|
|
19
|
+
const baseConfig: LagebildIngressConfig = {
|
|
20
|
+
apiUrl: "https://lagebild-api.example.workers.dev",
|
|
21
|
+
apiKey: "lbk_test_key_0001",
|
|
22
|
+
tenantId: "ten_warpgogol000001",
|
|
23
|
+
sourceSystemId: "site_warpgogol_com",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
test("buildIdempotencyKey is stable and combines tenant + submission", () => {
|
|
27
|
+
const key = buildIdempotencyKey("ten_abc", "sub_123");
|
|
28
|
+
expect(key).toBe("ten_abc:sub_123");
|
|
29
|
+
expect(buildIdempotencyKey("ten_abc", "sub_123")).toBe(key);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("submitIngress sends POST with correct headers and payload", async () => {
|
|
33
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
34
|
+
new Response(
|
|
35
|
+
JSON.stringify({
|
|
36
|
+
submission_id: "sub_test00000000000001",
|
|
37
|
+
accepted: true,
|
|
38
|
+
is_replay: false,
|
|
39
|
+
correlation_id: null,
|
|
40
|
+
}),
|
|
41
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const result = await submitIngress(baseConfig, {
|
|
46
|
+
submissionId: "sub_test00000000000001",
|
|
47
|
+
idempotencyKey: "ten_warpgogol000001:sub_test00000000000001",
|
|
48
|
+
interactionKind: "contact_message",
|
|
49
|
+
identityClaims: [
|
|
50
|
+
{ claim_type: "email", value: "test@example.com" },
|
|
51
|
+
{ claim_type: "person_name", value: "Test User" },
|
|
52
|
+
],
|
|
53
|
+
message: "Hello from the test",
|
|
54
|
+
origin: { page_uri: "https://example.com/contact", locale: "de" },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(result.accepted).toBe(true);
|
|
58
|
+
expect(result.is_replay).toBe(false);
|
|
59
|
+
|
|
60
|
+
expect(fetchSpy).toHaveBeenCalledOnce();
|
|
61
|
+
const [url, init] = fetchSpy.mock.calls[0] as [string, RequestInit];
|
|
62
|
+
expect(url).toBe("https://lagebild-api.example.workers.dev/v1/ingress");
|
|
63
|
+
expect(init.method).toBe("POST");
|
|
64
|
+
expect((init.headers as Record<string, string>)["Authorization"]).toBe(
|
|
65
|
+
"Bearer lbk_test_key_0001",
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const body = JSON.parse(init.body as string) as Record<string, unknown>;
|
|
69
|
+
expect(body["contract_version"]).toBe(LAGEBILD_INGRESS_CONTRACT_VERSION);
|
|
70
|
+
expect(body["tenant_id"]).toBe("ten_warpgogol000001");
|
|
71
|
+
expect(body["source_system_id"]).toBe("site_warpgogol_com");
|
|
72
|
+
expect(body["interaction_kind"]).toBe("contact_message");
|
|
73
|
+
|
|
74
|
+
fetchSpy.mockRestore();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("submitIngress returns accepted=false on network error", async () => {
|
|
78
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("connection refused"));
|
|
79
|
+
|
|
80
|
+
const result = await submitIngress(baseConfig, {
|
|
81
|
+
submissionId: "sub_test00000000000002",
|
|
82
|
+
idempotencyKey: "ten:test2",
|
|
83
|
+
interactionKind: "inquiry",
|
|
84
|
+
identityClaims: [{ claim_type: "email", value: "test@example.com" }],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(result.accepted).toBe(false);
|
|
88
|
+
expect(result.rejection_class).toBe("network_error");
|
|
89
|
+
|
|
90
|
+
vi.restoreAllMocks();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("submitIngress returns accepted=false on 400 response", async () => {
|
|
94
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
95
|
+
new Response(
|
|
96
|
+
JSON.stringify({
|
|
97
|
+
submission_id: "sub_test00000000000003",
|
|
98
|
+
accepted: false,
|
|
99
|
+
is_replay: false,
|
|
100
|
+
rejection_class: "schema_invalid",
|
|
101
|
+
correlation_id: null,
|
|
102
|
+
}),
|
|
103
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const result = await submitIngress(baseConfig, {
|
|
108
|
+
submissionId: "sub_test00000000000003",
|
|
109
|
+
idempotencyKey: "ten:test3",
|
|
110
|
+
interactionKind: "inquiry",
|
|
111
|
+
identityClaims: [{ claim_type: "email", value: "bad" }],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result.accepted).toBe(false);
|
|
115
|
+
expect(result.rejection_class).toBe("schema_invalid");
|
|
116
|
+
|
|
117
|
+
vi.restoreAllMocks();
|
|
118
|
+
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { MetricsPusher } from "./pusher.ts";
|
|
18
|
-
import { WARPGOGOL_METRIC_REGISTRY } from "./metric-registry.ts";
|
|
18
|
+
import type { WARPGOGOL_METRIC_REGISTRY } from "./metric-registry.ts";
|
|
19
19
|
|
|
20
20
|
type LabelKeys<L extends readonly string[]> = L[number];
|
|
21
21
|
type LabelMap<L extends readonly string[]> = Partial<Record<LabelKeys<L>, string>>;
|
|
@@ -135,15 +135,15 @@
|
|
|
135
135
|
"layer": "component"
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
|
-
"id": "
|
|
139
|
-
"displayName": "
|
|
140
|
-
"semanticRole": "component-
|
|
138
|
+
"id": "cycle-toggle",
|
|
139
|
+
"displayName": "Cycle Toggle",
|
|
140
|
+
"semanticRole": "component-cycle-toggle",
|
|
141
141
|
"version": "1.0.0",
|
|
142
142
|
"layoutHint": "split",
|
|
143
143
|
"acceptedCosmicNames": [
|
|
144
|
-
"
|
|
144
|
+
"Cupid"
|
|
145
145
|
],
|
|
146
|
-
"sourceFile": "packages/werkstatt-site/src/domain/ontology/archetypes/components/
|
|
146
|
+
"sourceFile": "packages/werkstatt-site/src/domain/ontology/archetypes/components/cycle-toggle.yaml",
|
|
147
147
|
"layer": "component"
|
|
148
148
|
},
|
|
149
149
|
{
|
|
@@ -750,7 +750,7 @@
|
|
|
750
750
|
"component-brand-label",
|
|
751
751
|
"component-copyright",
|
|
752
752
|
"component-currency-aware-price-display",
|
|
753
|
-
"component-
|
|
753
|
+
"component-cycle-toggle",
|
|
754
754
|
"component-footer-promo",
|
|
755
755
|
"component-lang-switcher",
|
|
756
756
|
"component-layout",
|
|
@@ -814,9 +814,10 @@
|
|
|
814
814
|
"Proteus": "@warpgogol/werkstatt-site/ui/components/brand-label",
|
|
815
815
|
"Charon": "@warpgogol/werkstatt-site/ui/components/copyright",
|
|
816
816
|
"Rosalind": "@warpgogol/werkstatt-site/ui/components/currency-aware-price-display",
|
|
817
|
-
"
|
|
817
|
+
"Cupid": "@warpgogol/werkstatt-site/ui/components/cycle-toggle",
|
|
818
818
|
"Galatea": "@warpgogol/werkstatt-site/ui/components/donation-card",
|
|
819
819
|
"Puck": "@warpgogol/werkstatt-site/ui/components/effects",
|
|
820
|
+
"Prospero": "@warpgogol/werkstatt-site/ui/components/external-link-qr-modal",
|
|
820
821
|
"Nereid": "@warpgogol/werkstatt-site/ui/components/footer-promo",
|
|
821
822
|
"Ophelia": "@warpgogol/werkstatt-site/ui/components/lang-switcher",
|
|
822
823
|
"Umbriel": "@warpgogol/werkstatt-site/ui/components/layout",
|
|
@@ -892,8 +893,9 @@
|
|
|
892
893
|
"brand-label": "Proteus",
|
|
893
894
|
"copyright": "Charon",
|
|
894
895
|
"currency-aware-price-display": "Rosalind",
|
|
895
|
-
"
|
|
896
|
+
"cycle-toggle": "Cupid",
|
|
896
897
|
"section-shell": "Puck",
|
|
898
|
+
"external-link-qr-modal": "Prospero",
|
|
897
899
|
"footer-promo": "Nereid",
|
|
898
900
|
"lang-switcher": "Ophelia",
|
|
899
901
|
"layout": "Umbriel",
|
|
@@ -975,9 +977,10 @@
|
|
|
975
977
|
"Ariel": "breadcrumbs",
|
|
976
978
|
"Charon": "copyright",
|
|
977
979
|
"Rosalind": "price-display",
|
|
978
|
-
"
|
|
980
|
+
"Cupid": "cycle-toggle",
|
|
979
981
|
"Galatea": "person-profile",
|
|
980
982
|
"Puck": "section-shell",
|
|
983
|
+
"Prospero": "layout-shell",
|
|
981
984
|
"Titania": "footer",
|
|
982
985
|
"Nereid": "footer-promo",
|
|
983
986
|
"Oberon": "header",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
schemaVersion: 1.0.0
|
|
2
|
-
totalCount:
|
|
2
|
+
totalCount: 59
|
|
3
3
|
entries:
|
|
4
4
|
- id: approach
|
|
5
5
|
displayName: Approach
|
|
@@ -100,14 +100,14 @@ entries:
|
|
|
100
100
|
- Rosalind
|
|
101
101
|
sourceFile: packages/werkstatt-site/src/domain/ontology/archetypes/components/currency-aware-price-display.yaml
|
|
102
102
|
layer: component
|
|
103
|
-
- id:
|
|
104
|
-
displayName:
|
|
105
|
-
semanticRole: component-
|
|
103
|
+
- id: cycle-toggle
|
|
104
|
+
displayName: Cycle Toggle
|
|
105
|
+
semanticRole: component-cycle-toggle
|
|
106
106
|
version: 1.0.0
|
|
107
107
|
layoutHint: split
|
|
108
108
|
acceptedCosmicNames:
|
|
109
|
-
-
|
|
110
|
-
sourceFile: packages/werkstatt-site/src/domain/ontology/archetypes/components/
|
|
109
|
+
- Cupid
|
|
110
|
+
sourceFile: packages/werkstatt-site/src/domain/ontology/archetypes/components/cycle-toggle.yaml
|
|
111
111
|
layer: component
|
|
112
112
|
- id: donation-card
|
|
113
113
|
displayName: Donation Card
|
|
@@ -163,6 +163,15 @@ entries:
|
|
|
163
163
|
- Nereid
|
|
164
164
|
sourceFile: packages/werkstatt-site/src/domain/ontology/archetypes/components/footer-promo.yaml
|
|
165
165
|
layer: component
|
|
166
|
+
- id: gratitude
|
|
167
|
+
displayName: Gratitude
|
|
168
|
+
semanticRole: client-gratitude
|
|
169
|
+
version: 1.0.0
|
|
170
|
+
layoutHint: card-grid
|
|
171
|
+
acceptedCosmicNames:
|
|
172
|
+
- Gonggong
|
|
173
|
+
sourceFile: packages/werkstatt-site/src/domain/ontology/archetypes/sections/gratitude.yaml
|
|
174
|
+
layer: section
|
|
166
175
|
- id: hero
|
|
167
176
|
displayName: Hero
|
|
168
177
|
semanticRole: hero
|
|
@@ -528,6 +537,7 @@ sectionRoles:
|
|
|
528
537
|
- article-index
|
|
529
538
|
- audience-self-identification
|
|
530
539
|
- breadcrumbs
|
|
540
|
+
- client-gratitude
|
|
531
541
|
- client-ownership-disclosure
|
|
532
542
|
- consent-gated-chat-launcher
|
|
533
543
|
- credits-gallery
|
|
@@ -560,7 +570,7 @@ componentRoles:
|
|
|
560
570
|
- component-brand-label
|
|
561
571
|
- component-copyright
|
|
562
572
|
- component-currency-aware-price-display
|
|
563
|
-
- component-
|
|
573
|
+
- component-cycle-toggle
|
|
564
574
|
- component-footer-promo
|
|
565
575
|
- component-lang-switcher
|
|
566
576
|
- component-layout
|
|
@@ -599,6 +609,7 @@ planetImportPaths:
|
|
|
599
609
|
Elara: "@warpgogol/werkstatt-site/ui/sections/dynamic-status-block"
|
|
600
610
|
Atlas: "@warpgogol/werkstatt-site/ui/sections/faq-list"
|
|
601
611
|
Dione: "@warpgogol/werkstatt-site/ui/sections/final-cta"
|
|
612
|
+
Gonggong: "@warpgogol/werkstatt-site/ui/sections/gratitude"
|
|
602
613
|
Europa: "@warpgogol/werkstatt-site/ui/sections/hero"
|
|
603
614
|
Phobos: "@warpgogol/werkstatt-site/ui/sections/hero-decision-card"
|
|
604
615
|
Ganymede: "@warpgogol/werkstatt-site/ui/sections/impact"
|
|
@@ -622,9 +633,10 @@ planetImportPaths:
|
|
|
622
633
|
Proteus: "@warpgogol/werkstatt-site/ui/components/brand-label"
|
|
623
634
|
Charon: "@warpgogol/werkstatt-site/ui/components/copyright"
|
|
624
635
|
Rosalind: "@warpgogol/werkstatt-site/ui/components/currency-aware-price-display"
|
|
625
|
-
|
|
636
|
+
Cupid: "@warpgogol/werkstatt-site/ui/components/cycle-toggle"
|
|
626
637
|
Galatea: "@warpgogol/werkstatt-site/ui/components/donation-card"
|
|
627
638
|
Puck: "@warpgogol/werkstatt-site/ui/components/effects"
|
|
639
|
+
Prospero: "@warpgogol/werkstatt-site/ui/components/external-link-qr-modal"
|
|
628
640
|
Nereid: "@warpgogol/werkstatt-site/ui/components/footer-promo"
|
|
629
641
|
Ophelia: "@warpgogol/werkstatt-site/ui/components/lang-switcher"
|
|
630
642
|
Umbriel: "@warpgogol/werkstatt-site/ui/components/layout"
|
|
@@ -675,6 +687,7 @@ blockTypeToCosmicName:
|
|
|
675
687
|
dynamic-status-block: Elara
|
|
676
688
|
faq-list: Atlas
|
|
677
689
|
final-cta: Dione
|
|
690
|
+
gratitude: Gonggong
|
|
678
691
|
hero: Europa
|
|
679
692
|
hero-decision-card: Phobos
|
|
680
693
|
impact: Ganymede
|
|
@@ -698,8 +711,9 @@ blockTypeToCosmicName:
|
|
|
698
711
|
brand-label: Proteus
|
|
699
712
|
copyright: Charon
|
|
700
713
|
currency-aware-price-display: Rosalind
|
|
701
|
-
|
|
714
|
+
cycle-toggle: Cupid
|
|
702
715
|
section-shell: Puck
|
|
716
|
+
external-link-qr-modal: Prospero
|
|
703
717
|
footer-promo: Nereid
|
|
704
718
|
lang-switcher: Ophelia
|
|
705
719
|
layout: Umbriel
|
|
@@ -754,6 +768,7 @@ roleByCosmicName:
|
|
|
754
768
|
Elara: data-driven-status-indicator
|
|
755
769
|
Atlas: approach
|
|
756
770
|
Dione: final-cta
|
|
771
|
+
Gonggong: client-gratitude
|
|
757
772
|
Europa: hero
|
|
758
773
|
Phobos: hero
|
|
759
774
|
Ganymede: impact
|
|
@@ -778,9 +793,10 @@ roleByCosmicName:
|
|
|
778
793
|
Ariel: breadcrumbs
|
|
779
794
|
Charon: copyright
|
|
780
795
|
Rosalind: price-display
|
|
781
|
-
|
|
796
|
+
Cupid: cycle-toggle
|
|
782
797
|
Galatea: person-profile
|
|
783
798
|
Puck: section-shell
|
|
799
|
+
Prospero: layout-shell
|
|
784
800
|
Titania: footer
|
|
785
801
|
Nereid: footer-promo
|
|
786
802
|
Oberon: header
|
|
@@ -95,8 +95,8 @@ levels:
|
|
|
95
95
|
de: "Welche Angaben helfen, den Betrieb einzuschätzen: Qualifikationen, Meisterbetrieb, Fotos, Bewertungen, Öffnungszeiten."
|
|
96
96
|
uk: "Які відомості допомагають оцінити бізнес: кваліфікації, майстерність, фото, відгуки, години роботи."
|
|
97
97
|
catalogHeading:
|
|
98
|
-
de:
|
|
99
|
-
uk:
|
|
98
|
+
de: Gewerbe auswählen
|
|
99
|
+
uk: Обрати галузь
|
|
100
100
|
productPrice:
|
|
101
101
|
heading:
|
|
102
102
|
de: "Gleiche Grundlage, angepasste Struktur"
|
|
@@ -104,7 +104,7 @@ levels:
|
|
|
104
104
|
body:
|
|
105
105
|
de: "Die Branche verändert nicht automatisch den Basispreis. Zusätzliche Seiten, Sprachen, Buchungsfunktionen oder Integrationen werden gesondert vereinbart."
|
|
106
106
|
uk: "Галузь не змінює автоматично базову ціну. Додаткові сторінки, мови, функції бронювання чи інтеграції узгоджуються окремо."
|
|
107
|
-
priceRef:
|
|
107
|
+
priceRef: business-profile.offerings/digital-foundation.presentation.price
|
|
108
108
|
finalCta:
|
|
109
109
|
heading:
|
|
110
110
|
de: Ihr Gewerbe ist noch nicht aufgeführt?
|
|
@@ -141,14 +141,14 @@ levels:
|
|
|
141
141
|
minModuleMappings: 3
|
|
142
142
|
minUniqueFaq: 5
|
|
143
143
|
claimRestrictions:
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
144
|
+
- mehr Anfragen
|
|
145
|
+
- mehr Termin-Anfragen
|
|
146
|
+
- echte Aufträge
|
|
147
|
+
- steigt die Wahrscheinlichkeit
|
|
148
|
+
- weniger Streuverluste
|
|
149
|
+
- höhere Conversion
|
|
150
|
+
- besser gefunden
|
|
151
|
+
- stärkstes Conversion-Signal
|
|
152
152
|
doorwayMaxFlaggedShare: 0.30
|
|
153
153
|
duplicateMaxSimilarity: 0.70
|
|
154
154
|
mode: warn
|
|
@@ -22,7 +22,7 @@ axes:
|
|
|
22
22
|
|
|
23
23
|
levels:
|
|
24
24
|
- depth: 0
|
|
25
|
-
slug: { de:
|
|
25
|
+
slug: { de: leistungen/website-service, uk: posluhy/sait-service }
|
|
26
26
|
constellation: website-service-root
|
|
27
27
|
geo: twin-only
|
|
28
28
|
semanticType: collection
|
|
@@ -51,25 +51,25 @@ levels:
|
|
|
51
51
|
minFaq: 5
|
|
52
52
|
minPageStructure: 1
|
|
53
53
|
claimRestrictions:
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
54
|
+
- mehr Anfragen
|
|
55
|
+
- mehr Termin-Anfragen
|
|
56
|
+
- mehr Buchungen
|
|
57
|
+
- echte Aufträge
|
|
58
|
+
- steigt die Wahrscheinlichkeit
|
|
59
|
+
- weniger Streuverluste
|
|
60
|
+
- höhere Conversion
|
|
61
|
+
- besser gefunden
|
|
62
|
+
- stärkstes Conversion-Signal
|
|
63
|
+
- garantierte Ergebnisse
|
|
64
|
+
- guaranteed results
|
|
65
|
+
- no. 1
|
|
66
|
+
- number one
|
|
67
|
+
- leading provider
|
|
68
|
+
- top rated
|
|
69
|
+
- beste Preis
|
|
70
|
+
- best price
|
|
71
|
+
- cheapest
|
|
72
|
+
- günstigste
|
|
73
73
|
mode: warn
|
|
74
74
|
|
|
75
75
|
policy:
|
|
@@ -2,8 +2,8 @@ id: lead.submit
|
|
|
2
2
|
version: 1
|
|
3
3
|
kind: action
|
|
4
4
|
title:
|
|
5
|
-
de:
|
|
6
|
-
en:
|
|
5
|
+
de: Anfrage senden
|
|
6
|
+
en: Submit a lead
|
|
7
7
|
description:
|
|
8
8
|
de: "Reicht eine Kontaktanfrage (Name, E-Mail, Nachricht) beim Gewerbe ein. Die Anfrage wird asynchron zugestellt — die Bestätigung signalisiert Annahme, nicht Zustellung."
|
|
9
9
|
en: "Submit a contact request (name, email, message) to the business. The request is delivered asynchronously — the confirmation signals acceptance, not delivery."
|
package/src/ontology/enums.ts
CHANGED
|
@@ -87,7 +87,7 @@ export type SemanticRole = string;
|
|
|
87
87
|
* section-cta | [RFC-0104] Single CTA / CTA group primitive
|
|
88
88
|
* section-image | [RFC-0104] Authored image primitive with fade masks
|
|
89
89
|
* site-background | [RFC-0105] Full-viewport background shell-layer component
|
|
90
|
-
*
|
|
90
|
+
* cycle-toggle | [RFC-0946] Generic cycle toggle button with localStorage persistence
|
|
91
91
|
* price-display | [RFC-0743] Currency-aware price display with pre-rendered variants
|
|
92
92
|
* scroll-to-top | [RFC-0768] Floating scroll-to-top button with Lenis smooth scrolling
|
|
93
93
|
*/
|
|
@@ -107,7 +107,7 @@ export const ComponentRoleValues = [
|
|
|
107
107
|
"section-cta",
|
|
108
108
|
"section-image",
|
|
109
109
|
"site-background",
|
|
110
|
-
"
|
|
110
|
+
"cycle-toggle",
|
|
111
111
|
"price-display",
|
|
112
112
|
"scroll-to-top",
|
|
113
113
|
] as const;
|
|
@@ -105,31 +105,31 @@ surfacePolicy:
|
|
|
105
105
|
# Enforced by surface.media-leakage.validate and surface.contract.validate.
|
|
106
106
|
mediaLeakagePolicy:
|
|
107
107
|
prohibitedStrings:
|
|
108
|
-
- pattern:
|
|
108
|
+
- pattern: Gemini
|
|
109
109
|
matchingStrategy: context-aware
|
|
110
110
|
contextSelector: "[data-credit-context], figcaption, details, dl"
|
|
111
111
|
reason: "AI model name is internal metadata, not visitor-facing"
|
|
112
|
-
- pattern:
|
|
112
|
+
- pattern: AIPlatform
|
|
113
113
|
matchingStrategy: whole-word
|
|
114
|
-
reason:
|
|
115
|
-
- pattern:
|
|
114
|
+
reason: Internal enum value (CreditPartyKind)
|
|
115
|
+
- pattern: Organization
|
|
116
116
|
matchingStrategy: context-aware
|
|
117
117
|
contextSelector: "[data-credit-context], figcaption, details, dl"
|
|
118
|
-
reason:
|
|
119
|
-
- pattern:
|
|
118
|
+
reason: Internal enum value (CreditPartyKind) — only as media author label
|
|
119
|
+
- pattern: commissioned-warpgogol-material
|
|
120
120
|
matchingStrategy: whole-word
|
|
121
|
-
reason:
|
|
122
|
-
- pattern:
|
|
121
|
+
reason: Internal enum value (MaterialSourceType)
|
|
122
|
+
- pattern: linked-public-source
|
|
123
123
|
matchingStrategy: whole-word
|
|
124
|
-
reason:
|
|
125
|
-
- pattern:
|
|
124
|
+
reason: Internal enum value
|
|
125
|
+
- pattern: Copyright © 2026 Warpgogol
|
|
126
126
|
matchingStrategy: exact
|
|
127
127
|
contextSelector: ":not(footer):not([data-footer])"
|
|
128
|
-
reason:
|
|
128
|
+
reason: Repetitive copyright boilerplate — per-material usage status is on /bildnachweise/
|
|
129
129
|
requiredLabels:
|
|
130
|
-
- label:
|
|
130
|
+
- label: Konzeptillustration
|
|
131
131
|
lang: de
|
|
132
|
-
- label:
|
|
132
|
+
- label: Концептуальна ілюстрація
|
|
133
133
|
lang: uk
|
|
134
134
|
requiredLinkPattern: "/bildnachweise/#"
|
|
135
|
-
aiImageAttribute:
|
|
135
|
+
aiImageAttribute: data-ai-generated
|
package/src/ontology/index.ts
CHANGED
|
@@ -81,6 +81,7 @@ export {
|
|
|
81
81
|
SiteFamilyContract,
|
|
82
82
|
sectionArchetypeSchema,
|
|
83
83
|
systemManifestSchema,
|
|
84
|
+
systemCollectionSchema,
|
|
84
85
|
} from "./schemas/index.ts";
|
|
85
86
|
export type {
|
|
86
87
|
Constellation,
|
|
@@ -106,6 +107,7 @@ export type {
|
|
|
106
107
|
SystemManifest,
|
|
107
108
|
SystemPagePin,
|
|
108
109
|
SystemPlanetPin,
|
|
110
|
+
SystemCollectionData,
|
|
109
111
|
} from "./schemas/index.ts";
|
|
110
112
|
|
|
111
113
|
// ---------------------------------------------------------------------------
|
|
@@ -63,8 +63,13 @@ export type {
|
|
|
63
63
|
SectionArchetypeLayoutHint,
|
|
64
64
|
} from "./section-archetype.ts";
|
|
65
65
|
|
|
66
|
-
export { systemManifestSchema } from "./system.ts";
|
|
67
|
-
export type {
|
|
66
|
+
export { systemManifestSchema, systemCollectionSchema } from "./system.ts";
|
|
67
|
+
export type {
|
|
68
|
+
SystemManifest,
|
|
69
|
+
SystemPagePin,
|
|
70
|
+
SystemPlanetPin,
|
|
71
|
+
SystemCollectionData,
|
|
72
|
+
} from "./system.ts";
|
|
68
73
|
|
|
69
74
|
// Architecture review 2026-07-10: Re-export growthVendorSchema for @warpgogol/werkstatt-site/growth/config.ts.
|
|
70
75
|
export { growthVendorSchema } from "./system.ts";
|
|
@@ -128,7 +128,7 @@ export const systemIntegrationsSchema = z.object({
|
|
|
128
128
|
* (e.g. uchat, stripe, operator), and whether the pilot is `enabled`. UChat is the
|
|
129
129
|
* conversation runtime — it renders the funnel and requests transitions; it never
|
|
130
130
|
* owns the graph, pricing, or canonical state. Make.com is never a source. Validated
|
|
131
|
-
* by `funnel.contract.validate` / `funnel.stage.validate` / `funnel.
|
|
131
|
+
* by `funnel.contract.validate` / `funnel.stage.validate` / `funnel.crm.validate`.
|
|
132
132
|
* Absent ⇒ the funnel validators are a no-op pass (funnel not yet enabled for the app).
|
|
133
133
|
*/
|
|
134
134
|
funnel: z
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<CHANGE_SUMMARY>
|
|
9
9
|
<item>RFC-0303 Phase 3: extracted from schemas/system.ts as part of the domain split.</item>
|
|
10
10
|
<item>RFC-0377: added optional `audience` field to the per-page pin schema.</item>
|
|
11
|
+
<item>ADR-0062: added `ctaTarget` to identity, `sectionNav` + `ctaTarget` to page pins, created `systemCollectionSchema` for content collection typing.</item>
|
|
11
12
|
</CHANGE_SUMMARY>
|
|
12
13
|
*/
|
|
13
14
|
|
|
@@ -79,6 +80,13 @@ export const systemManifestSchema = z.object({
|
|
|
79
80
|
* brief.validate can cross-check the brief against the running app identity.
|
|
80
81
|
*/
|
|
81
82
|
domain: z.string().min(1).optional(),
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* ADR-0062: App-level default CTA target — the pageId of the contact/CTA page.
|
|
86
|
+
* Per-page `ctaTarget` in pages[] overrides this app-level default.
|
|
87
|
+
* Used by resolve-route.ts to populate `ctaTarget` in PageRouteData.
|
|
88
|
+
*/
|
|
89
|
+
ctaTarget: z.string().min(1).optional(),
|
|
82
90
|
}),
|
|
83
91
|
|
|
84
92
|
/**
|
|
@@ -222,7 +230,7 @@ export const systemManifestSchema = z.object({
|
|
|
222
230
|
enabled: z.boolean(),
|
|
223
231
|
cosmicMoon: z.string().min(1),
|
|
224
232
|
pin: z.string().min(1),
|
|
225
|
-
props: z.record(z.string(), z.
|
|
233
|
+
props: z.record(z.string(), z.unknown()).optional(),
|
|
226
234
|
})
|
|
227
235
|
.optional(),
|
|
228
236
|
})
|
|
@@ -247,6 +255,19 @@ export const systemManifestSchema = z.object({
|
|
|
247
255
|
)
|
|
248
256
|
.optional()
|
|
249
257
|
.default([]),
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* RFC-0922 / ADR-0062: per-page section nav toggle. When false, the
|
|
261
|
+
* page template skips extractSectionNavItems and the Header secondary
|
|
262
|
+
* nav strip is omitted. Defaults to true when absent.
|
|
263
|
+
*/
|
|
264
|
+
sectionNav: z.boolean().optional(),
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* ADR-0062: per-page CTA target override. When present, overrides the
|
|
268
|
+
* app-level `identity.ctaTarget` for this page.
|
|
269
|
+
*/
|
|
270
|
+
ctaTarget: z.string().min(1).optional(),
|
|
250
271
|
}),
|
|
251
272
|
)
|
|
252
273
|
.optional()
|
|
@@ -498,3 +519,19 @@ export type SystemManifest = z.infer<typeof systemManifestSchema>;
|
|
|
498
519
|
export type SystemPagePin = NonNullable<SystemManifest["pages"]>[number];
|
|
499
520
|
/** A single planet pin within a page-route entry. */
|
|
500
521
|
export type SystemPlanetPin = NonNullable<SystemManifest["pages"][number]["planets"]>[number];
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* ADR-0062: Lenient schema for the `system` Astro content collection.
|
|
525
|
+
*
|
|
526
|
+
* `systemManifestSchema` is the strict validation schema used by `system.manifest.validate`.
|
|
527
|
+
* The content collection schema needs to be more lenient:
|
|
528
|
+
* - `.partial()` — all top-level fields are optional (system.md content varies per site)
|
|
529
|
+
* - `.loose()` — unknown top-level keys pass through (e.g. `policy`, `seo`)
|
|
530
|
+
*
|
|
531
|
+
* This schema is used in `src/content/config.ts` via `defineCollection({ schema: systemCollectionSchema })`.
|
|
532
|
+
* It provides compile-time typing for `getCollection("system")` consumers, eliminating `as any` casts.
|
|
533
|
+
*/
|
|
534
|
+
export const systemCollectionSchema = systemManifestSchema.partial().loose();
|
|
535
|
+
|
|
536
|
+
/** Inferred type for the `system` content collection data. */
|
|
537
|
+
export type SystemCollectionData = z.infer<typeof systemCollectionSchema>;
|
|
@@ -49,5 +49,10 @@ export {
|
|
|
49
49
|
pageOutputSchema,
|
|
50
50
|
} from "./system/page-output.ts";
|
|
51
51
|
|
|
52
|
-
export { systemManifestSchema } from "./system/manifest.ts";
|
|
53
|
-
export type {
|
|
52
|
+
export { systemManifestSchema, systemCollectionSchema } from "./system/manifest.ts";
|
|
53
|
+
export type {
|
|
54
|
+
SystemManifest,
|
|
55
|
+
SystemPagePin,
|
|
56
|
+
SystemPlanetPin,
|
|
57
|
+
SystemCollectionData,
|
|
58
|
+
} from "./system/manifest.ts";
|
|
@@ -155,6 +155,17 @@ export const EFFECT_STACK_ITEM_SCHEMA = {
|
|
|
155
155
|
skewX: { type: "number", minimum: -15, maximum: 15 },
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
type: "object",
|
|
160
|
+
additionalProperties: false,
|
|
161
|
+
required: ["kind", "enabled"],
|
|
162
|
+
properties: {
|
|
163
|
+
kind: { const: "lighten" },
|
|
164
|
+
enabled: { type: "boolean" },
|
|
165
|
+
opacity: { type: "number", minimum: 0, maximum: 1 },
|
|
166
|
+
color: EFFECT_COLOR_SCHEMA,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
158
169
|
],
|
|
159
170
|
};
|
|
160
171
|
|
|
@@ -88,7 +88,7 @@ export const SECTION_VISUAL_FRAGMENT: JsonSchemaFragment = {
|
|
|
88
88
|
},
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
|
-
density: { enum: ["compact", "normal", "spacious"] },
|
|
91
|
+
density: { enum: ["compact", "normal", "spacious", "flush"] },
|
|
92
92
|
tone: { enum: ["default", "warning", "success", "muted"] },
|
|
93
93
|
containerVariant: { enum: ["default", "narrow", "full"] },
|
|
94
94
|
motion: {
|