@synap-core/workspace-templates 0.9.2 → 0.9.3
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/dist/index.d.ts +170 -8
- package/dist/index.js +643 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/business-developer.yaml +24 -6
- package/src/content-os.yaml +19 -0
- package/src/crm.yaml +28 -2
- package/src/define.primary-surface.test.ts +183 -0
- package/src/define.ts +142 -19
- package/src/index.ts +26 -3
- package/src/marketing.yaml +19 -0
- package/src/templates.ts +126 -8
- package/src/types.ts +74 -5
- package/src/validate-flow.drift.test.ts +83 -0
- package/src/validate-flow.ts +555 -0
- package/src/validate.test.ts +121 -0
- package/src/validate.ts +133 -10
package/src/templates.ts
CHANGED
|
@@ -6833,14 +6833,13 @@ const DATA = {
|
|
|
6833
6833
|
"valueType": "string",
|
|
6834
6834
|
"inputType": "select",
|
|
6835
6835
|
"enumValues": [
|
|
6836
|
-
"
|
|
6837
|
-
"referral",
|
|
6836
|
+
"cold-outreach",
|
|
6838
6837
|
"linkedin",
|
|
6839
|
-
"
|
|
6838
|
+
"twitter",
|
|
6839
|
+
"landing-page",
|
|
6840
|
+
"inbound-call",
|
|
6841
|
+
"referral",
|
|
6840
6842
|
"event",
|
|
6841
|
-
"inbound",
|
|
6842
|
-
"partner",
|
|
6843
|
-
"import",
|
|
6844
6843
|
"other"
|
|
6845
6844
|
]
|
|
6846
6845
|
},
|
|
@@ -7210,6 +7209,37 @@ const DATA = {
|
|
|
7210
7209
|
"label": "Notes",
|
|
7211
7210
|
"valueType": "string",
|
|
7212
7211
|
"inputType": "textarea"
|
|
7212
|
+
},
|
|
7213
|
+
{
|
|
7214
|
+
"slug": "campaign-type",
|
|
7215
|
+
"label": "Type",
|
|
7216
|
+
"valueType": "string",
|
|
7217
|
+
"inputType": "select",
|
|
7218
|
+
"enumValues": [
|
|
7219
|
+
"scoping",
|
|
7220
|
+
"outreach",
|
|
7221
|
+
"live",
|
|
7222
|
+
"partnership",
|
|
7223
|
+
"event",
|
|
7224
|
+
"other"
|
|
7225
|
+
],
|
|
7226
|
+
"constraints": {
|
|
7227
|
+
"defaultValue": "outreach"
|
|
7228
|
+
}
|
|
7229
|
+
},
|
|
7230
|
+
{
|
|
7231
|
+
"slug": "campaign-color",
|
|
7232
|
+
"label": "Color",
|
|
7233
|
+
"valueType": "string",
|
|
7234
|
+
"inputType": "select",
|
|
7235
|
+
"enumValues": [
|
|
7236
|
+
"indigo",
|
|
7237
|
+
"violet",
|
|
7238
|
+
"amber",
|
|
7239
|
+
"cyan",
|
|
7240
|
+
"pink",
|
|
7241
|
+
"teal"
|
|
7242
|
+
]
|
|
7213
7243
|
}
|
|
7214
7244
|
]
|
|
7215
7245
|
}
|
|
@@ -8909,6 +8939,37 @@ const DATA = {
|
|
|
8909
8939
|
"label": "Notes",
|
|
8910
8940
|
"valueType": "string",
|
|
8911
8941
|
"inputType": "textarea"
|
|
8942
|
+
},
|
|
8943
|
+
{
|
|
8944
|
+
"slug": "campaign-type",
|
|
8945
|
+
"label": "Type",
|
|
8946
|
+
"valueType": "string",
|
|
8947
|
+
"inputType": "select",
|
|
8948
|
+
"enumValues": [
|
|
8949
|
+
"scoping",
|
|
8950
|
+
"outreach",
|
|
8951
|
+
"live",
|
|
8952
|
+
"partnership",
|
|
8953
|
+
"event",
|
|
8954
|
+
"other"
|
|
8955
|
+
],
|
|
8956
|
+
"constraints": {
|
|
8957
|
+
"defaultValue": "outreach"
|
|
8958
|
+
}
|
|
8959
|
+
},
|
|
8960
|
+
{
|
|
8961
|
+
"slug": "campaign-color",
|
|
8962
|
+
"label": "Color",
|
|
8963
|
+
"valueType": "string",
|
|
8964
|
+
"inputType": "select",
|
|
8965
|
+
"enumValues": [
|
|
8966
|
+
"indigo",
|
|
8967
|
+
"violet",
|
|
8968
|
+
"amber",
|
|
8969
|
+
"cyan",
|
|
8970
|
+
"pink",
|
|
8971
|
+
"teal"
|
|
8972
|
+
]
|
|
8912
8973
|
}
|
|
8913
8974
|
]
|
|
8914
8975
|
},
|
|
@@ -10009,6 +10070,12 @@ const DATA = {
|
|
|
10009
10070
|
],
|
|
10010
10071
|
"isPublic": true
|
|
10011
10072
|
},
|
|
10073
|
+
"app": {
|
|
10074
|
+
"kind": "url",
|
|
10075
|
+
"appId": "crm",
|
|
10076
|
+
"url": "https://crm.synap.live",
|
|
10077
|
+
"title": "CRM"
|
|
10078
|
+
},
|
|
10012
10079
|
"dependencies": [
|
|
10013
10080
|
{
|
|
10014
10081
|
"slug": "foundation",
|
|
@@ -10079,7 +10146,17 @@ const DATA = {
|
|
|
10079
10146
|
"slug": "source",
|
|
10080
10147
|
"label": "Source",
|
|
10081
10148
|
"valueType": "string",
|
|
10082
|
-
"inputType": "
|
|
10149
|
+
"inputType": "select",
|
|
10150
|
+
"enumValues": [
|
|
10151
|
+
"cold-outreach",
|
|
10152
|
+
"linkedin",
|
|
10153
|
+
"twitter",
|
|
10154
|
+
"landing-page",
|
|
10155
|
+
"inbound-call",
|
|
10156
|
+
"referral",
|
|
10157
|
+
"event",
|
|
10158
|
+
"other"
|
|
10159
|
+
]
|
|
10083
10160
|
},
|
|
10084
10161
|
{
|
|
10085
10162
|
"slug": "icpScore",
|
|
@@ -10168,7 +10245,17 @@ const DATA = {
|
|
|
10168
10245
|
"slug": "source",
|
|
10169
10246
|
"label": "Source",
|
|
10170
10247
|
"valueType": "string",
|
|
10171
|
-
"inputType": "
|
|
10248
|
+
"inputType": "select",
|
|
10249
|
+
"enumValues": [
|
|
10250
|
+
"cold-outreach",
|
|
10251
|
+
"linkedin",
|
|
10252
|
+
"twitter",
|
|
10253
|
+
"landing-page",
|
|
10254
|
+
"inbound-call",
|
|
10255
|
+
"referral",
|
|
10256
|
+
"event",
|
|
10257
|
+
"other"
|
|
10258
|
+
]
|
|
10172
10259
|
},
|
|
10173
10260
|
{
|
|
10174
10261
|
"slug": "icpScore",
|
|
@@ -22004,6 +22091,37 @@ const DATA = {
|
|
|
22004
22091
|
"label": "Notes",
|
|
22005
22092
|
"valueType": "string",
|
|
22006
22093
|
"inputType": "textarea"
|
|
22094
|
+
},
|
|
22095
|
+
{
|
|
22096
|
+
"slug": "campaign-type",
|
|
22097
|
+
"label": "Type",
|
|
22098
|
+
"valueType": "string",
|
|
22099
|
+
"inputType": "select",
|
|
22100
|
+
"enumValues": [
|
|
22101
|
+
"scoping",
|
|
22102
|
+
"outreach",
|
|
22103
|
+
"live",
|
|
22104
|
+
"partnership",
|
|
22105
|
+
"event",
|
|
22106
|
+
"other"
|
|
22107
|
+
],
|
|
22108
|
+
"constraints": {
|
|
22109
|
+
"defaultValue": "outreach"
|
|
22110
|
+
}
|
|
22111
|
+
},
|
|
22112
|
+
{
|
|
22113
|
+
"slug": "campaign-color",
|
|
22114
|
+
"label": "Color",
|
|
22115
|
+
"valueType": "string",
|
|
22116
|
+
"inputType": "select",
|
|
22117
|
+
"enumValues": [
|
|
22118
|
+
"indigo",
|
|
22119
|
+
"violet",
|
|
22120
|
+
"amber",
|
|
22121
|
+
"cyan",
|
|
22122
|
+
"pink",
|
|
22123
|
+
"teal"
|
|
22124
|
+
]
|
|
22007
22125
|
}
|
|
22008
22126
|
]
|
|
22009
22127
|
},
|
package/src/types.ts
CHANGED
|
@@ -21,13 +21,76 @@ export interface TemplateAppRef {
|
|
|
21
21
|
/** For kind='stored': the cell key registered in the cell registry */
|
|
22
22
|
cellKey?: string;
|
|
23
23
|
/** For kind='stored': default props passed to the cell */
|
|
24
|
-
props?: Record<string,
|
|
24
|
+
props?: Record<string, WorkspaceJsonValue>;
|
|
25
25
|
/** For kind='url': external URL to open */
|
|
26
26
|
url?: string;
|
|
27
27
|
/** Human label */
|
|
28
28
|
title?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Workspace primary surface — JSON-safe persisted subset of SurfaceDescriptor
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
export type WorkspaceJsonValue =
|
|
36
|
+
| string
|
|
37
|
+
| number
|
|
38
|
+
| boolean
|
|
39
|
+
| null
|
|
40
|
+
| WorkspaceJsonValue[]
|
|
41
|
+
| { [key: string]: WorkspaceJsonValue };
|
|
42
|
+
|
|
43
|
+
export type WorkspacePrimarySurface =
|
|
44
|
+
| {
|
|
45
|
+
kind: 'app';
|
|
46
|
+
appId: string;
|
|
47
|
+
rendererType: 'native';
|
|
48
|
+
title?: string;
|
|
49
|
+
props?: Record<string, WorkspaceJsonValue>;
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
kind: 'app';
|
|
53
|
+
appId: string;
|
|
54
|
+
rendererType: 'external';
|
|
55
|
+
url: string;
|
|
56
|
+
title?: string;
|
|
57
|
+
props?: Record<string, WorkspaceJsonValue>;
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
kind: 'cell';
|
|
61
|
+
cellKey: string;
|
|
62
|
+
title?: string;
|
|
63
|
+
props?: Record<string, WorkspaceJsonValue>;
|
|
64
|
+
}
|
|
65
|
+
| {
|
|
66
|
+
kind: 'view';
|
|
67
|
+
viewId: string;
|
|
68
|
+
title?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Authoring-time view reference. View IDs do not exist until provisioning, so
|
|
73
|
+
* templates and packages address a view by its stable authored name and/or
|
|
74
|
+
* slug. The pod resolves this to `WorkspacePrimarySurface` before persistence.
|
|
75
|
+
*/
|
|
76
|
+
export type WorkspacePrimarySurfaceViewReference =
|
|
77
|
+
| {
|
|
78
|
+
kind: 'view';
|
|
79
|
+
viewName: string;
|
|
80
|
+
viewSlug?: string;
|
|
81
|
+
title?: string;
|
|
82
|
+
}
|
|
83
|
+
| {
|
|
84
|
+
kind: 'view';
|
|
85
|
+
viewName?: string;
|
|
86
|
+
viewSlug: string;
|
|
87
|
+
title?: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type TemplatePrimarySurface =
|
|
91
|
+
| WorkspacePrimarySurface
|
|
92
|
+
| WorkspacePrimarySurfaceViewReference;
|
|
93
|
+
|
|
31
94
|
// ============================================================================
|
|
32
95
|
// Property / Profile / View (mirrors WorkspaceProposal from onboarding)
|
|
33
96
|
// ============================================================================
|
|
@@ -53,8 +116,7 @@ export interface TemplateProfile {
|
|
|
53
116
|
* Visibility of the profile DEFINITION — who can USE this vocabulary.
|
|
54
117
|
* `system` = pod-wide; `shared` = ONE row reachable from other workspaces via
|
|
55
118
|
* a `profile_workspace_access` grant; omit or `workspace` = this workspace
|
|
56
|
-
* only. Orthogonal to `entityScope` below — do not conflate the two axes.
|
|
57
|
-
*/
|
|
119
|
+
* only. Orthogonal to `entityScope` below — do not conflate the two axes. */
|
|
58
120
|
scope?: string;
|
|
59
121
|
/**
|
|
60
122
|
* Placement of the profile's ENTITY ROWS — where the data LANDS.
|
|
@@ -76,7 +138,7 @@ export interface TemplateProfile {
|
|
|
76
138
|
/** Semantic slug for system profiles. */
|
|
77
139
|
semanticSlug?: string;
|
|
78
140
|
/** Kind vs role profile. Omit for default ('kind'). */
|
|
79
|
-
profileKind?:
|
|
141
|
+
profileKind?: 'kind' | 'role';
|
|
80
142
|
/** For profileKind='role': which base-kind profile slugs this role can attach to. */
|
|
81
143
|
applicableKinds?: string[];
|
|
82
144
|
properties: TemplateProperty[];
|
|
@@ -173,7 +235,14 @@ export interface TemplateSidebarItem {
|
|
|
173
235
|
export interface TemplateLayoutConfig {
|
|
174
236
|
pinnedApps?: string[];
|
|
175
237
|
defaultView?: string;
|
|
176
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* Canonical first screen for this workspace.
|
|
240
|
+
*
|
|
241
|
+
* `undefined` means the template has no opinion during reconciliation,
|
|
242
|
+
* `null` explicitly returns the workspace to its home surface, and a
|
|
243
|
+
* descriptor replaces the current primary surface. */
|
|
244
|
+
primarySurface?: TemplatePrimarySurface | null;
|
|
245
|
+
/** Compatibility input only. New templates should use `app:` or `primarySurface`. */
|
|
177
246
|
defaultApp?: string | null;
|
|
178
247
|
theme?: string;
|
|
179
248
|
sidebarItems?: TemplateSidebarItem[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift guard for the TEMPORARY flow-validator duplication.
|
|
3
|
+
*
|
|
4
|
+
* `validate-flow.ts` in this package is a pure copy of synap-backend's
|
|
5
|
+
* `packages/api/src/services/automations/validate-flow.ts` (the runtime SSOT that
|
|
6
|
+
* carries the `@synap/database` compile-time guard). The copy exists only because
|
|
7
|
+
* backend/CP consume this package over a FROZEN published npm boundary, so backend
|
|
8
|
+
* cannot yet re-export from here (see both files' headers for the cutover plan).
|
|
9
|
+
*
|
|
10
|
+
* While two copies exist, this test BINDS them: it reads BOTH files and asserts
|
|
11
|
+
* their validator LOGIC is byte-identical — modulo the backend-only bits that
|
|
12
|
+
* legitimately exist only there (the `import type ... @synap/database` line and
|
|
13
|
+
* the `AssertExact` drift-guard block) and all comments. If a future edit changes
|
|
14
|
+
* one copy and not the other, this fails and points at the cutover.
|
|
15
|
+
*
|
|
16
|
+
* It self-retires: once backend's file becomes a re-export (cutover done, no
|
|
17
|
+
* `function validateFlowDefinition` body left), the duplication is gone and the
|
|
18
|
+
* test skips. It also skips in an isolated checkout where synap-backend is absent.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, expect, it } from "vitest";
|
|
22
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const OWN_COPY = join(HERE, "validate-flow.ts");
|
|
28
|
+
// src → workspace-templates → packages → synap-app → <repo root>
|
|
29
|
+
const BACKEND_COPY = join(
|
|
30
|
+
HERE,
|
|
31
|
+
"../../../../synap-backend/packages/api/src/services/automations/validate-flow.ts"
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reduce a validate-flow source file to just its comparable LOGIC:
|
|
36
|
+
* - drop the backend-only `@synap/database` type import,
|
|
37
|
+
* - drop the backend-only compile-time drift guard (`AssertExact` + the two
|
|
38
|
+
* `_*InSync` bindings + their `void` uses),
|
|
39
|
+
* - strip all comments (the file headers differ by design), blank lines, and
|
|
40
|
+
* normalize whitespace.
|
|
41
|
+
*/
|
|
42
|
+
function normalizeFlowValidatorSource(src: string): string {
|
|
43
|
+
return src
|
|
44
|
+
.replace(/import type \{[^}]*\} from ["']@synap\/database["'];/g, "")
|
|
45
|
+
.replace(/type AssertExact<[\s\S]*?void _outputTypesInSync;/g, "")
|
|
46
|
+
.replace(/\/\*[\s\S]*?\*\//g, "") // block comments (incl. JSDoc)
|
|
47
|
+
.replace(/\/\/.*$/gm, "") // line + trailing comments
|
|
48
|
+
.split("\n")
|
|
49
|
+
.map((l) => l.replace(/\s+/g, " ").trim())
|
|
50
|
+
.filter((l) => l.length > 0)
|
|
51
|
+
.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("validate-flow.ts — no drift between the two temporary copies", () => {
|
|
55
|
+
it("workspace-templates copy is logic-identical to synap-backend's SSOT", () => {
|
|
56
|
+
if (!existsSync(BACKEND_COPY)) {
|
|
57
|
+
// Isolated checkout (synap-backend not a sibling). Nothing to bind against.
|
|
58
|
+
console.warn(
|
|
59
|
+
`[drift] synap-backend copy not found at ${BACKEND_COPY} — skipping cross-repo drift check.`
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const backendSrc = readFileSync(BACKEND_COPY, "utf-8");
|
|
65
|
+
if (!/function validateFlowDefinition/.test(backendSrc)) {
|
|
66
|
+
// Cutover done: backend re-exports from this package, duplication resolved.
|
|
67
|
+
console.warn(
|
|
68
|
+
"[drift] synap-backend validate-flow.ts has no validateFlowDefinition body — appears to re-export (cutover complete); drift check retired."
|
|
69
|
+
);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const own = normalizeFlowValidatorSource(readFileSync(OWN_COPY, "utf-8"));
|
|
74
|
+
const backend = normalizeFlowValidatorSource(backendSrc);
|
|
75
|
+
|
|
76
|
+
expect(
|
|
77
|
+
own,
|
|
78
|
+
"workspace-templates/src/validate-flow.ts has drifted from synap-backend's " +
|
|
79
|
+
"validate-flow.ts. Re-sync the two copies (or perform the cutover to a single " +
|
|
80
|
+
"re-export — see the file headers)."
|
|
81
|
+
).toBe(backend);
|
|
82
|
+
});
|
|
83
|
+
});
|