@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/src/templates.ts CHANGED
@@ -6833,14 +6833,13 @@ const DATA = {
6833
6833
  "valueType": "string",
6834
6834
  "inputType": "select",
6835
6835
  "enumValues": [
6836
- "manual",
6837
- "referral",
6836
+ "cold-outreach",
6838
6837
  "linkedin",
6839
- "email",
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": "text"
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": "text"
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, unknown>;
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?: "kind" | "role";
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
- /** The first-screen app — auto-opened on workspace switch */
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
+ });