@synap-core/workspace-templates 0.11.0 → 0.23.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.
Files changed (249) hide show
  1. package/README.md +483 -0
  2. package/automations/bookmark-enrichment.automation.json +321 -0
  3. package/automations/github-pr-events.automation.json +505 -0
  4. package/automations/mail-feed.automation.json +86 -0
  5. package/automations/ship-a-change-checks-green.automation.json +106 -0
  6. package/automations/ship-a-change-checks-red.automation.json +106 -0
  7. package/automations/ship-a-change-deployed.automation.json +56 -0
  8. package/automations/ship-a-change-pr-merged.automation.json +110 -0
  9. package/automations/ship-a-change-pr-opened.automation.json +104 -0
  10. package/automations/url-bookmark-capture.automation.json +132 -0
  11. package/capabilities/agency-skills.capability.json +157 -0
  12. package/capabilities/apify.capability.json +265 -0
  13. package/capabilities/apollo.capability.json +120 -0
  14. package/capabilities/arch-backend.capability.json +97 -0
  15. package/capabilities/arch-client-intelligence.capability.json +401 -0
  16. package/capabilities/backblaze-b2.capability.json +205 -0
  17. package/capabilities/cal-com.capability.json +266 -0
  18. package/capabilities/canva.capability.json +274 -0
  19. package/capabilities/claude-code-routines.capability.json +172 -0
  20. package/capabilities/claude-managed-agents.capability.json +215 -0
  21. package/capabilities/client-comms-report.capability.json +246 -0
  22. package/capabilities/content-idea-to-production.capability.json +29 -0
  23. package/capabilities/datagouv.capability.json +315 -0
  24. package/capabilities/discord-bot.capability.json +157 -0
  25. package/capabilities/dropbox.capability.json +281 -0
  26. package/capabilities/exa.capability.json +109 -0
  27. package/capabilities/fal-ai.capability.json +83 -0
  28. package/capabilities/fal-media-lab.capability.json +158 -0
  29. package/capabilities/fireflies.capability.json +191 -0
  30. package/capabilities/freellmapi.capability.json +114 -0
  31. package/capabilities/generic-apikey.capability.json +92 -0
  32. package/capabilities/github-merge.capability.json +99 -0
  33. package/capabilities/github-read.capability.json +325 -0
  34. package/capabilities/governance-tighten-recommender.capability.json +81 -0
  35. package/capabilities/jev.capability.json +226 -0
  36. package/capabilities/lead-conversion-enrollment.capability.json +246 -0
  37. package/capabilities/mail-feed.capability.json +277 -0
  38. package/capabilities/mailgun.capability.json +52 -0
  39. package/capabilities/nango-google.capability.json +639 -0
  40. package/capabilities/notion.capability.json +417 -0
  41. package/capabilities/omniroute.capability.json +137 -0
  42. package/capabilities/posthog-analytics.capability.json +258 -0
  43. package/capabilities/postiz.capability.json +467 -0
  44. package/capabilities/proactive-digest.capability.json +190 -0
  45. package/capabilities/proton.capability.json +288 -0
  46. package/capabilities/replicate-rvm.capability.json +97 -0
  47. package/capabilities/research-methods.capability.json +57 -0
  48. package/capabilities/shopify.capability.json +395 -0
  49. package/capabilities/slack.capability.json +369 -0
  50. package/capabilities/stripe.capability.json +420 -0
  51. package/capabilities/telegram-bridge.capability.json +56 -0
  52. package/capabilities/treg.capability.json +102 -0
  53. package/capabilities/unipile-linkedin.capability.json +471 -0
  54. package/capabilities/vercel.capability.json +424 -0
  55. package/dist/define-CPS0n8BV.d.ts +1331 -0
  56. package/dist/index.d.ts +189 -900
  57. package/dist/index.js +24654 -21639
  58. package/dist/index.js.map +1 -1
  59. package/dist/wire.d.ts +222 -0
  60. package/dist/wire.js +77 -0
  61. package/dist/wire.js.map +1 -0
  62. package/package.json +41 -27
  63. package/packs/bookmark-views-pack.json +33 -0
  64. package/packs/crm-pack.json +76 -0
  65. package/packs/customer-profile.json +28 -0
  66. package/packs/deep-research.playbook.json +30 -0
  67. package/packs/event-views-pack.json +33 -0
  68. package/packs/habit-profile.json +27 -0
  69. package/packs/hr-pack.json +54 -0
  70. package/packs/invoice-profile.json +28 -0
  71. package/packs/knowledge-base-pack.json +56 -0
  72. package/packs/meeting-profile.json +29 -0
  73. package/packs/product-profile.json +30 -0
  74. package/packs/project-management-pack.json +49 -0
  75. package/packs/recipe-profile.json +29 -0
  76. package/packs/task-views-pack.json +39 -0
  77. package/retired.yaml +136 -0
  78. package/skills/app-factory-build-slice/SKILL.md +70 -0
  79. package/skills/app-factory-build-slice.catalog.json +17 -0
  80. package/skills/app-factory-design-canvas/SKILL.md +96 -0
  81. package/skills/app-factory-design-canvas.catalog.json +17 -0
  82. package/skills/app-factory-design-review/SKILL.md +99 -0
  83. package/skills/app-factory-design-review.catalog.json +18 -0
  84. package/skills/app-factory-research/SKILL.md +123 -0
  85. package/skills/app-factory-research.catalog.json +17 -0
  86. package/skills/bookmark-enrichment/SKILL.md +40 -0
  87. package/skills/carousel-format/carousel-format.skill.json +18 -0
  88. package/skills/collage-broll/collage-broll.skill.json +20 -0
  89. package/skills/content-taste/content-taste.skill.json +18 -0
  90. package/skills/creative-director/creative-director.skill.json +18 -0
  91. package/skills/design-critique/SKILL.md +135 -0
  92. package/skills/design-critique.catalog.json +12 -0
  93. package/skills/designing-with-real-components/SKILL.md +140 -0
  94. package/skills/designing-with-real-components.catalog.json +12 -0
  95. package/skills/edit-loop/edit-loop.skill.json +25 -0
  96. package/skills/facecam-edit/facecam-edit.skill.json +24 -0
  97. package/skills/mapping-the-server/SKILL.md +54 -0
  98. package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
  99. package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
  100. package/skills/open-design-edit/open-design-edit.skill.json +24 -0
  101. package/skills/reading-client-progress/SKILL.md +65 -0
  102. package/skills/routing-to-channels/SKILL.md +54 -0
  103. package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
  104. package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
  105. package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
  106. package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
  107. package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
  108. package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
  109. package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
  110. package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
  111. package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
  112. package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
  113. package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
  114. package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
  115. package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
  116. package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
  117. package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
  118. package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
  119. package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
  120. package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
  121. package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
  122. package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
  123. package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
  124. package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
  125. package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
  126. package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
  127. package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
  128. package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
  129. package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
  130. package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
  131. package/skills/video-direction/video-direction.skill.json +20 -0
  132. package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
  133. package/skills/video-toolbox/video-toolbox.skill.json +20 -0
  134. package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
  135. package/src/agency-os.yaml +122 -0
  136. package/src/agent-os-skill-slugs.test.ts +65 -0
  137. package/src/apify-social-reader.test.ts +317 -0
  138. package/src/base.template.test.ts +25 -19
  139. package/src/base.yaml +95 -39
  140. package/src/blockchain-ecosystem.yaml +1 -0
  141. package/src/brand-library.yaml +525 -14
  142. package/src/builder-dev-fold.template.test.ts +113 -0
  143. package/src/builder.yaml +798 -92
  144. package/src/business-model-catalog-description.test.ts +47 -0
  145. package/src/business-model-grp.template.test.ts +214 -0
  146. package/src/business-model.yaml +598 -0
  147. package/src/catalog-safety.test.ts +198 -0
  148. package/src/catalog.template-items.test.ts +75 -0
  149. package/src/catalog.ts +85 -2
  150. package/src/civic-france.yaml +102 -0
  151. package/src/concept-guard.test.ts +28 -8
  152. package/src/concept-guard.ts +2 -2
  153. package/src/content-creation.template.test.ts +52 -0
  154. package/src/content-creation.yaml +192 -0
  155. package/src/content-os.edit-loop.template.test.ts +210 -0
  156. package/src/content-os.publish-due-posts.template.test.ts +261 -0
  157. package/src/content-os.template.test.ts +322 -0
  158. package/src/content-os.video-takes.template.test.ts +338 -0
  159. package/src/content-os.work-a-take.template.test.ts +125 -0
  160. package/src/content-os.yaml +1830 -39
  161. package/src/content-studio-retired.template.test.ts +82 -0
  162. package/src/corpus-flow-validity.test.ts +4 -5
  163. package/src/crm-absorbs-outreach.template.test.ts +117 -0
  164. package/src/crm-build-plan.template.test.ts +128 -0
  165. package/src/crm-lists.template.test.ts +96 -0
  166. package/src/crm-messages-cadence.template.test.ts +164 -0
  167. package/src/crm-one-send-door.template.test.ts +65 -0
  168. package/src/crm.yaml +1456 -181
  169. package/src/decisions-wave.template.test.ts +186 -0
  170. package/src/define.ts +130 -17
  171. package/src/design-to-implementation.template.test.ts +94 -0
  172. package/src/dist-freshness.test.ts +97 -0
  173. package/src/domain-template-type.test.ts +75 -0
  174. package/src/ecosystem.yaml +27 -66
  175. package/src/enterprise-os.yaml +10 -134
  176. package/src/facecam-edit.audio-studio.test.ts +188 -0
  177. package/src/fal-generate-image.test.ts +200 -0
  178. package/src/fal-media-lab-pod-files.test.ts +180 -0
  179. package/src/finance.yaml +254 -1
  180. package/src/foundation.yaml +130 -28
  181. package/src/frame-host-modules.d.mts +6 -0
  182. package/src/frame-host-modules.mjs +58 -0
  183. package/src/generate.mjs +9 -3
  184. package/src/github-merge.test.ts +210 -0
  185. package/src/grants.yaml +57 -17
  186. package/src/hr.yaml +39 -6
  187. package/src/index.ts +36 -1
  188. package/src/intent.test.ts +161 -0
  189. package/src/internal-runbook.yaml +19 -1
  190. package/src/jev.test.ts +356 -0
  191. package/src/legal.yaml +33 -1
  192. package/src/life-os.yaml +65 -240
  193. package/src/lifecycle-coverage.test.ts +124 -0
  194. package/src/lifecycle-declarations.test.ts +252 -0
  195. package/src/lifecycle-presets.test.ts +468 -0
  196. package/src/lifecycle-presets.ts +494 -0
  197. package/src/lifecycle-stuck.test.ts +46 -0
  198. package/src/lifecycle-waits.parity.test.ts +319 -0
  199. package/src/marketing.yaml +108 -4
  200. package/src/meetings-knowledge.yaml +18 -0
  201. package/src/networking.yaml +23 -2
  202. package/src/operations.yaml +304 -4
  203. package/src/pipeline-views.template.test.ts +88 -0
  204. package/src/playbook-goal-placeholders.test.ts +57 -0
  205. package/src/postiz-contract.test.ts +134 -0
  206. package/src/presentation-declarations.test.ts +174 -0
  207. package/src/process-contract.validate.test.ts +352 -0
  208. package/src/project-management.yaml +39 -740
  209. package/src/publish-body.d.mts +29 -0
  210. package/src/publish-body.mjs +74 -0
  211. package/src/publish-body.test.ts +74 -0
  212. package/src/publish-official-lib.test.ts +176 -0
  213. package/src/replicate-rvm.test.ts +504 -0
  214. package/src/research-base.yaml +119 -20
  215. package/src/research-decision.yaml +60 -3
  216. package/src/reserved-profile-slugs.test.ts +83 -0
  217. package/src/retired-goal-kinds.test.ts +58 -0
  218. package/src/role-reference-and-step-domain.test.ts +192 -0
  219. package/src/seed-obligation.test.ts +176 -0
  220. package/src/send-message-maps.test.ts +227 -0
  221. package/src/shared-role-ssot.test.ts +42 -1
  222. package/src/ship-a-change.template.test.ts +177 -0
  223. package/src/skills.test.ts +170 -0
  224. package/src/social.yaml +85 -27
  225. package/src/space-packages.freshness.test.ts +97 -0
  226. package/src/space-packages.generated.ts +40 -0
  227. package/src/space-packages.test.ts +125 -0
  228. package/src/space-packages.ts +73 -0
  229. package/src/start.test.ts +188 -0
  230. package/src/task-intents.test.ts +293 -0
  231. package/src/templates.ts +21639 -21522
  232. package/src/types.ts +443 -60
  233. package/src/unipile-publish-post.test.ts +162 -0
  234. package/src/validate-flow.drift.test.ts +2 -3
  235. package/src/validate-flow.ts +32 -2
  236. package/src/validate-onboarding.test.ts +59 -0
  237. package/src/validate.test.ts +156 -3
  238. package/src/validate.ts +1384 -7
  239. package/src/wire-core.ts +231 -0
  240. package/src/wire.test.ts +159 -0
  241. package/src/wire.ts +107 -0
  242. package/src/__fixtures__/report-automation-flow.json +0 -393
  243. package/src/agent-fleet.yaml +0 -1645
  244. package/src/business-developer.template.test.ts +0 -351
  245. package/src/business-developer.yaml +0 -860
  246. package/src/content-studio.yaml +0 -361
  247. package/src/dev-dashboard.yaml +0 -990
  248. package/src/outreach-comms.yaml +0 -486
  249. package/src/the-arch.yaml +0 -201
package/src/social.yaml CHANGED
@@ -13,6 +13,11 @@ meta:
13
13
  color: "#1D9BF0"
14
14
  tags: [social, distribution, publishing, channels]
15
15
  isPublic: true
16
+ # What this space PRODUCES (homes): the purpose word, then its kinds/roles.
17
+ intent:
18
+ label: Distribution
19
+ produces:
20
+ - profile: post
16
21
 
17
22
  # Social REUSES Content OS's pod-wide `platform` (publishing-account) vocabulary
18
23
  # instead of forking its own `channel` kind. `platform` is `scope: shared` in
@@ -23,7 +28,7 @@ dependencies:
23
28
  - slug: content-os
24
29
  kind: workspace
25
30
  relation: require
26
- reason: "Reuses Content OS's shared platform (publishing-account) vocabulary."
31
+ reason: "Reuses Content OS's shared platform (publishing-account) and post vocabulary."
27
32
 
28
33
  workspace:
29
34
  name: Social
@@ -125,44 +130,97 @@ profiles:
125
130
  - slug: platform-tags
126
131
  label: Tags
127
132
  valueType: array
133
+ # ── Postiz channel (the `postiz` capability) ─────────────────────────
134
+ # Which Postiz channel this account is, both read from
135
+ # postiz_list_integrations: its id, and its provider (the `identifier`
136
+ # a post's `settings.__type` names: x, linkedin, instagram…). A post
137
+ # queued on this account passes both to postiz_schedule_post.
138
+ - slug: postiz-integration-id
139
+ label: Postiz channel ID
140
+ valueType: string
141
+ inputType: text
142
+ - slug: postiz-provider
143
+ label: Postiz provider
144
+ valueType: string
145
+ inputType: text
128
146
 
129
- - slug: social-post
147
+ # ── Post (shared post kind — SSOT is content-os.yaml) ──────────────────────
148
+ # RE-DECLARED, like `platform` above: this declaration writes the grant that
149
+ # puts the ONE pod-wide `post` kind in this workspace. The old `social-post`
150
+ # twin is folded into it by the w11.post.* manifest ops (plan D2); its
151
+ # `failed` status became `publish-outcome` (plan D12). Only the properties
152
+ # Social uses are re-declared, each byte-identical to content-os's.
153
+ - slug: post
130
154
  displayName: Post
131
- icon: send
132
- color: "#0EA5E9"
133
- description: "A single piece going out on a channel — its content, its schedule, and where it landed."
155
+ icon: file-edit
156
+ color: "#6366F1"
157
+ description: "Your atomic content unit. One idea, multiple platform formats — tracked from spark to publication."
158
+ scope: shared
159
+ # The ONE lifecycle (work model L1): every option's category.
160
+ lifecycle:
161
+ property: post-status
162
+ options:
163
+ - { key: Idea, category: open }
164
+ - { key: Briefed, category: active }
165
+ - { key: Scripted, category: active }
166
+ - { key: "In Draft", category: active }
167
+ - { key: Review, category: active }
168
+ - { key: Scheduled, category: active }
169
+ - { key: Published, category: done }
170
+ - { key: Archived, category: lost, humanOnly: true }
171
+ # How it renders (entity rendering, decision 8): @synap-core/types/presentation.
172
+ presentation:
173
+ family: work
134
174
  properties:
135
175
  - slug: post-status
136
176
  label: Status
137
177
  valueType: string
138
178
  inputType: select
139
- enumValues: [draft, scheduled, published, failed]
140
- constraints: { defaultValue: draft }
141
- - slug: post-platform
142
- label: Platform
179
+ enumValues:
180
+ [
181
+ "Idea",
182
+ "Briefed",
183
+ "Scripted",
184
+ "In Draft",
185
+ "Review",
186
+ "Scheduled",
187
+ "Published",
188
+ "Archived",
189
+ ]
190
+ - slug: post-account
191
+ label: Account
143
192
  valueType: entity_id
144
193
  targetProfileSlug: platform
145
194
  inputType: entity_select
146
- - slug: post-content
147
- label: Content
195
+ - slug: publish-date
196
+ label: Publish Date
197
+ valueType: date
198
+ - slug: post-url
199
+ label: Published URL
200
+ valueType: string
201
+ inputType: text
202
+ placeholder: "https://..."
203
+ constraints: { format: url }
204
+ - slug: publish-outcome
205
+ label: Publish outcome
206
+ valueType: string
207
+ inputType: select
208
+ enumValues: [succeeded, failed, queued, sending, waiting]
209
+ - slug: publish-error
210
+ label: Publish error
148
211
  valueType: string
149
212
  inputType: textarea
150
- - slug: scheduled-at
151
- label: Scheduled For
152
- valueType: date
153
- inputType: date
154
213
  - slug: published-at
155
214
  label: Published At
156
215
  valueType: date
157
216
  inputType: date
158
- - slug: post-url
159
- label: Post URL
160
- valueType: string
161
- inputType: url
162
- - slug: post-notes
163
- label: Notes
217
+ # ── Postiz (the `postiz` capability) ─────────────────────────────────
218
+ # The post's id in the person's Postiz, recorded when it is queued there
219
+ # (postiz_schedule_post returns it). Postiz's webhook finds the post by it.
220
+ - slug: postiz-post-id
221
+ label: Postiz post ID
164
222
  valueType: string
165
- inputType: textarea
223
+ inputType: text
166
224
 
167
225
  - slug: post-schedule
168
226
  displayName: Schedule
@@ -238,9 +296,9 @@ views:
238
296
  - name: Content Calendar
239
297
  slug: content-calendar
240
298
  type: kanban
241
- scopeProfileSlug: social-post
299
+ scopeProfileSlug: post
242
300
  groupBy: post-status
243
- description: "Posts on a board by status — draft to published."
301
+ description: "Posts on a board by status — idea to published."
244
302
  - name: Platforms
245
303
  slug: platforms
246
304
  type: table
@@ -268,10 +326,10 @@ onboarding:
268
326
  what: The platforms you actually publish on — one per real account, with its type and handle.
269
327
  cardinality: few
270
328
  keyFields: [platform-type, handle]
271
- - profileSlug: social-post
272
- what: The posts in motion — drafts, scheduled, and recently published — each tied to its platform.
329
+ - profileSlug: post
330
+ what: The posts in motion — drafts, scheduled, and recently published — each tied to its account.
273
331
  cardinality: several
274
- keyFields: [post-status, post-platform]
332
+ keyFields: [post-status, post-account]
275
333
  openingQuestions:
276
334
  - "Which channels are you actually active on right now, and which one earns the most attention?"
277
335
  - "What's your posting cadence per channel — daily, weekly, ad-hoc?"
@@ -0,0 +1,97 @@
1
+ /**
2
+ * FRESHNESS GATE for the generated-and-committed `src/space-packages.generated.ts`.
3
+ *
4
+ * A space package bundles ONLY its own modules
5
+ * (`packages/web/src/space-packages/`). The shared components come from the
6
+ * host's `@synap/kit` frame module (`packages/web/src/space-packages/kit.ts`,
7
+ * built into `packages/web/src/cell-runtime/frame-host-modules.generated.ts`,
8
+ * whose own freshness test sits beside it), React from the host's React
9
+ * modules. The build is committed, so it can go stale: a package source
10
+ * changes, nobody rebuilds, and the space keeps shipping the old code. This
11
+ * test re-reads what the build recorded: every repo file the bundle read must
12
+ * still hash the same.
13
+ *
14
+ * It also pins the KIT CONTRACT: every name a package imports from
15
+ * `@synap/kit` is one the host's kit exports, so a kit change that drops a
16
+ * name fails here, not as a frame that cannot link.
17
+ *
18
+ * And it pins the declaration to the build: every template's
19
+ * `spacePackage.cells[]` is built (same key, same source), and nothing is
20
+ * built for a cell no template declares.
21
+ */
22
+ import { createHash } from 'node:crypto';
23
+ import { existsSync, readFileSync } from 'node:fs';
24
+ import { dirname, join, resolve } from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
26
+ import { describe, expect, it } from 'vitest';
27
+ import { readYamlTemplates } from './generate.mjs';
28
+ import { BUILT_SPACE_PACKAGES } from './space-packages.generated';
29
+ import { hostRequiresIn, kitImportsIn } from './frame-host-modules.mjs';
30
+
31
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
32
+ const REBUILD = 'pnpm --filter @synap-core/workspace-templates build:space-packages';
33
+ const SPACE_PACKAGES_DIR = 'packages/web/src/space-packages/';
34
+ const HOST_MODULES_FILE = join(REPO_ROOT, 'packages/web/src/cell-runtime/frame-host-modules.generated.ts');
35
+
36
+ function sha256(path: string): string {
37
+ return createHash('sha256').update(readFileSync(path)).digest('hex');
38
+ }
39
+
40
+ /** The names the host's kit exports, read from the host's generated file (no cross-package import). */
41
+ function hostKitExports(): string[] {
42
+ const m = /export const FRAME_KIT_EXPORTS: readonly string\[\] = (\[[^\]]*\]);/.exec(readFileSync(HOST_MODULES_FILE, 'utf8'));
43
+ if (!m) throw new Error(`no FRAME_KIT_EXPORTS in ${HOST_MODULES_FILE}`);
44
+ return JSON.parse(m[1]!) as string[];
45
+ }
46
+
47
+ const declared = readYamlTemplates().flatMap(({ slug, data }) =>
48
+ ((data?.spacePackage?.cells ?? []) as Array<{ key: string; source: string }>).map((c) => ({ slug, ...c }))
49
+ );
50
+
51
+ describe('space-packages.generated.ts freshness', () => {
52
+ it('builds exactly the cells the templates declare', () => {
53
+ const built = Object.entries(BUILT_SPACE_PACKAGES).flatMap(([slug, pkg]) =>
54
+ pkg.cells.map((c) => ({ slug, key: c.key, source: c.source }))
55
+ );
56
+ const id = (c: { slug: string; key: string; source: string }) => `${c.slug}:${c.key} ← ${c.source}`;
57
+ expect(built.map(id).sort(), `stale space packages — run: ${REBUILD}`).toEqual(declared.map(id).sort());
58
+ });
59
+
60
+ for (const [slug, pkg] of Object.entries(BUILT_SPACE_PACKAGES)) {
61
+ it(`${slug}: every repo file the bundle read is unchanged`, () => {
62
+ const changed = Object.entries(pkg.inputs)
63
+ .filter(([path, hash]) => !existsSync(join(REPO_ROOT, path)) || sha256(join(REPO_ROOT, path)) !== hash)
64
+ .map(([path]) => path);
65
+ expect(changed, `space package "${slug}" was built from an older source — run: ${REBUILD}`).toEqual([]);
66
+ });
67
+
68
+ it(`${slug}: it bundles only its own modules; the shared layers come from the host's kit`, () => {
69
+ const inputs = Object.keys(pkg.inputs);
70
+ expect(inputs.length).toBeGreaterThan(0);
71
+ expect(inputs.filter((p) => !p.startsWith(SPACE_PACKAGES_DIR))).toEqual([]);
72
+ expect(inputs).not.toContain(`${SPACE_PACKAGES_DIR}kit.ts`);
73
+ });
74
+
75
+ it(`${slug}: every name it imports from @synap/kit is one the host's kit exports`, () => {
76
+ const exported = hostKitExports();
77
+ for (const cell of pkg.cells) {
78
+ // The record is the code's own imports, not a hand-kept list.
79
+ expect(cell.kitImports, cell.key).toEqual(kitImportsIn(cell.code));
80
+ expect(cell.kitImports.length, cell.key).toBeGreaterThan(0);
81
+ const missing = cell.kitImports.filter((n) => !exported.includes(n));
82
+ expect(missing, `${slug}:${cell.key} imports names the host's kit no longer exports — run: ${REBUILD}`).toEqual([]);
83
+ }
84
+ });
85
+
86
+ it(`${slug}: the bundle runs as a network-contained frame module`, () => {
87
+ for (const cell of pkg.cells) {
88
+ // A frame has no network origin for code: esm.sh is in no frame's CSP.
89
+ expect(cell.code.includes('esm.sh'), cell.key).toBe(false);
90
+ expect(cell.code.length, cell.key).toBeGreaterThan(0);
91
+ // A CJS `require()` of a host module throws in the frame (esbuild's
92
+ // `__require` shim): the module would never link.
93
+ expect(hostRequiresIn(cell.code), cell.key).toEqual([]);
94
+ }
95
+ });
96
+ }
97
+ });
@@ -0,0 +1,40 @@
1
+ // GENERATED by scripts/build-space-packages.mjs from each template's
2
+ // `spacePackage` — do not edit. Rebuild:
3
+ // pnpm --filter @synap-core/workspace-templates build:space-packages
4
+ // `space-packages.freshness.test.ts` fails when this file no longer matches its sources.
5
+ import type { BuiltSpacePackage } from './space-packages';
6
+
7
+ export const BUILT_SPACE_PACKAGES: Record<string, BuiltSpacePackage> = {
8
+ "builder-workspace": {
9
+ "cells": [
10
+ {
11
+ "key": "home",
12
+ "name": "Work",
13
+ "source": "packages/web/src/space-packages/builder/home.tsx",
14
+ "code": "import{useMemo as $}from\"react\";import{installFrameBridge as q}from\"@synap/kit\";import{useCallback as D,useEffect as O,useMemo as T,useState as _}from\"react\";import{LensPageView as x,SpatialAppHeader as M,SpatialButton as W,SpatialPageFrame as j,resolveActionLabel as V,resolveDestinationLabel as Y,resolveObjectNoun as z}from\"@synap/kit\";import{useCallback as E,useEffect as f,useState as g}from\"react\";import{SYNAP_CHANGED_EVENT as b,useLensPageClock as R,useLensPageModelOf as B}from\"@synap/kit\";var C=\"lens.page\",F=750;function H(e){if(!e||typeof e!=\"object\")return new Error(\"The lens page read returned nothing\");let t=e;return typeof t.error==\"string\"?new Error(t.error):!t.page||typeof t.page!=\"object\"?new Error(\"The lens page read returned no page\"):t.page}function S(e,t){let n=R(t),{since:o}=n,[i,c]=g({status:\"loading\",page:null,error:null}),[m,u]=g(0);f(()=>{if(!t)return;let s=!0;return c(r=>({...r,status:r.page?r.status:\"loading\"})),e.query(C,o?{since:o}:{}).then(r=>{if(!s)return;let p=H(r);c(p instanceof Error?{status:\"failed\",page:null,error:p.message}:{status:\"ready\",page:p,error:null})}).catch(r=>{s&&c({status:\"failed\",page:null,error:String(r)})}),()=>{s=!1}},[e,t,o,m]),f(()=>{if(!t)return;let s,r=()=>{s===void 0&&(s=setTimeout(()=>{s=void 0,u(v=>v+1)},F))},p=()=>{document.visibilityState===\"visible\"&&r()};document.addEventListener(\"visibilitychange\",p),window.addEventListener(\"focus\",r);let h=e.subscribe(\"entity:changed\",r),P=e.subscribe(b,r);return()=>{clearTimeout(s),document.removeEventListener(\"visibilitychange\",p),window.removeEventListener(\"focus\",r),h(),P()}},[e,t]);let a=B(i.page,n),l=E(()=>u(s=>s+1),[]);return{model:a,status:a?i.status:i.status===\"failed\"?\"failed\":\"loading\",error:i.error,refetch:l,today:n.today}}import{encodeActivityScope as I,encodeLensScope as y,lensActivityScope as A}from\"@synap/kit\";function N(e,t){let n=y(e);return{blocking:{kind:\"app\",appId:\"inbox\",props:{scope:n}},happening:{kind:\"app\",appId:\"sessions\",props:{view:\"sessions\",scope:n,narrow:\"running\"}},produced:{kind:\"app\",appId:\"data\",props:{view:\"landed\",scope:I(A(e))}},happened:{kind:\"app\",appId:\"sessions\",props:{view:\"activity\",day:t,scope:n}}}}function L(e){return{kind:\"app\",appId:\"sessions\",props:{view:\"plan\",scope:y(e)}}}function k(e,t,n){let o=N(t,n);return{blocking:()=>e(o.blocking),happening:()=>e(o.happening),produced:()=>e(o.produced),happened:()=>e(o.happened)}}import{jsx as d}from\"react/jsx-runtime\";var G=\"Open a decision to answer it on its own page.\";function U(e){let[t,n]=_(null);return O(()=>{e.onInit(o=>n({scope:o.workspaceId?{kind:\"workspace\",workspaceId:o.workspaceId}:{kind:\"pod\"},name:o.workspaceName?.trim()||null}))},[e]),t}function w({bridge:e}){let t=U(e),n=t?.scope??null,o=S(e,n),i=D((a,l)=>e.openSurface({kind:\"object\",objectKind:a.kind,objectId:a.id,...a.view?{view:a.view}:{},...l?{title:l}:{}}),[e]),c=T(()=>n?k(a=>e.openSurface(a),n,o.today):{},[e,n,o.today]),m=Y(\"work\"),u=o.status===\"ready\"&&(o.model?.blocking.items.length??0)>0;return d(j,{className:\"space-work-home\",template:\"hub\",layout:\"column\",width:\"list\",header:d(M,{size:\"lg\",title:t?.name??z(\"workspace\"),...u?{subtitle:G}:{},...n?{actions:d(W,{size:\"sm\",variant:\"secondary\",onClick:()=>e.openSurface(L(n)),children:`${V(\"open\")} in ${m}`})}:{}}),children:d(x,{scope:n??{kind:\"pod\"},model:n?o.model:null,status:n?o.status:\"loading\",onRetry:o.refetch,today:o.today,doors:c,onOpen:i,columns:!0})})}import{jsx as Q}from\"react/jsx-runtime\";function K(){let e=$(()=>q(),[]);return Q(w,{bridge:e})}export{K as default};\n",
15
+ "kitImports": [
16
+ "LensPageView",
17
+ "SYNAP_CHANGED_EVENT",
18
+ "SpatialAppHeader",
19
+ "SpatialButton",
20
+ "SpatialPageFrame",
21
+ "encodeActivityScope",
22
+ "encodeLensScope",
23
+ "installFrameBridge",
24
+ "lensActivityScope",
25
+ "resolveActionLabel",
26
+ "resolveDestinationLabel",
27
+ "resolveObjectNoun",
28
+ "useLensPageClock",
29
+ "useLensPageModelOf"
30
+ ]
31
+ }
32
+ ],
33
+ "inputs": {
34
+ "packages/web/src/space-packages/builder/home.tsx": "7afed696a23b1a474eace193d85688233e2f652e5e4ca56926edc8e1a16e62ef",
35
+ "packages/web/src/space-packages/frame-doors.ts": "662fbd70165f21a758b8677ac7a1169030f76996921e322159ccd67e19ee7520",
36
+ "packages/web/src/space-packages/frame-lens.ts": "627d26ad593a327f4d3cb50ceb3f04529200f99bb97719d02edc346ff60c2073",
37
+ "packages/web/src/space-packages/SpaceWorkHome.tsx": "e44497878da09b9ac0e9356440196e5973329a5ddef462991d9deb7087444718"
38
+ }
39
+ }
40
+ };
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { toPackageDefinition, toWorkspaceDefinition } from './define.js';
3
+ import { spacePackageCells, type BuiltSpacePackage } from './space-packages.js';
4
+ import type { WorkspaceYaml } from './types.js';
5
+ import { validateTemplate } from './validate.js';
6
+
7
+ const SOURCE = 'packages/web/src/space-packages/fixture/home.tsx';
8
+
9
+ function template(overrides: Partial<Pick<WorkspaceYaml, 'app' | 'layout' | 'spacePackage'>>): WorkspaceYaml {
10
+ return {
11
+ meta: {
12
+ slug: 'space-package-test',
13
+ name: 'Space package test',
14
+ description: 'Space package fixture',
15
+ icon: 'panels-top-left',
16
+ color: 'blue',
17
+ },
18
+ workspace: { name: 'Space package test', description: 'Space package fixture' },
19
+ profiles: [{ slug: 'thing', displayName: 'Thing', properties: [] }],
20
+ ...overrides,
21
+ };
22
+ }
23
+
24
+ const withPackage = (app?: WorkspaceYaml['app']) =>
25
+ template({
26
+ ...(app ? { app } : {}),
27
+ spacePackage: { cells: [{ key: 'home', name: 'Home', source: SOURCE }] },
28
+ });
29
+
30
+ const built: Record<string, BuiltSpacePackage> = {
31
+ 'space-package-test': {
32
+ cells: [{ key: 'home', name: 'Home', source: SOURCE, code: 'export default function Home() { return null; }', kitImports: [] }],
33
+ inputs: {},
34
+ },
35
+ };
36
+
37
+ const rules = (tpl: WorkspaceYaml) => validateTemplate(tpl).errors.map((e) => e.rule);
38
+
39
+ describe('space package cells', () => {
40
+ it('carries each built module as a network-contained frame cell', () => {
41
+ expect(spacePackageCells(withPackage(), built)).toEqual([
42
+ { key: 'home', name: 'Home', code: built['space-package-test'].cells[0].code, rendererType: 'frame' },
43
+ ]);
44
+ });
45
+
46
+ it('has no cells for a template without a space package', () => {
47
+ expect(spacePackageCells(template({}), built)).toBeUndefined();
48
+ });
49
+
50
+ it('refuses a declared cell that was never built: no space opens "Unknown cell"', () => {
51
+ expect(() => spacePackageCells(withPackage(), {})).toThrow(/not built/);
52
+ const otherSource = { 'space-package-test': { ...built['space-package-test'], cells: [{ ...built['space-package-test'].cells[0], source: 'packages/web/src/space-packages/fixture/old.tsx' }] } };
53
+ expect(() => spacePackageCells(withPackage(), otherSource)).toThrow(/not built/);
54
+ });
55
+
56
+ it('travels on the package definition only: the workspace door has no cells', () => {
57
+ expect('cells' in toWorkspaceDefinition(withPackage())).toBe(false);
58
+ expect('cells' in toPackageDefinition(template({}))).toBe(false);
59
+ });
60
+ });
61
+
62
+ describe('space package validation', () => {
63
+ it('accepts an entry that opens a cell the template installs', () => {
64
+ expect(rules(withPackage({ kind: 'stored', cellKey: 'cell:space-package-test:home' }))).not.toContain(
65
+ 'space-package-entry-unknown'
66
+ );
67
+ });
68
+
69
+ it('refuses an entry naming a package cell the template does not build', () => {
70
+ expect(rules(withPackage({ kind: 'stored', cellKey: 'cell:space-package-test:other' }))).toContain(
71
+ 'space-package-entry-unknown'
72
+ );
73
+ expect(rules(template({ app: { kind: 'stored', cellKey: 'cell:space-package-test:home' } }))).toContain(
74
+ 'space-package-entry-unknown'
75
+ );
76
+ // Another package's namespace is never this template's to open.
77
+ expect(rules(withPackage({ kind: 'stored', cellKey: 'cell:crm:home' }))).toContain('space-package-entry-unknown');
78
+ });
79
+
80
+ it('checks the authored layout.primarySurface the same way', () => {
81
+ const tpl = template({
82
+ layout: { primarySurface: { kind: 'cell', cellKey: 'cell:space-package-test:home' } },
83
+ });
84
+ expect(rules(tpl)).toContain('space-package-entry-unknown');
85
+ });
86
+
87
+ it('leaves built-in cell keys alone', () => {
88
+ expect(rules(template({ app: { kind: 'stored', cellKey: 'content-home' } }))).not.toContain(
89
+ 'space-package-entry-unknown'
90
+ );
91
+ });
92
+
93
+ it('builds only from the shared components under packages/web/src/space-packages/', () => {
94
+ const bad = (source: string) =>
95
+ rules(template({ spacePackage: { cells: [{ key: 'home', name: 'Home', source }] } }));
96
+ expect(bad('apps/devplane/page.tsx')).toContain('space-package-cell-source');
97
+ expect(bad('packages/web/src/space-packages/../../../../etc/x.ts')).toContain('space-package-cell-source');
98
+ expect(bad('packages/web/src/space-packages/x/home.css')).toContain('space-package-cell-source');
99
+ expect(bad(SOURCE)).not.toContain('space-package-cell-source');
100
+ });
101
+
102
+ it('needs unique kebab-case keys', () => {
103
+ const tpl = template({
104
+ spacePackage: {
105
+ cells: [
106
+ { key: 'home', name: 'Home', source: SOURCE },
107
+ { key: 'home', name: 'Home again', source: SOURCE },
108
+ { key: 'Home Page', name: 'Bad', source: SOURCE },
109
+ ],
110
+ },
111
+ });
112
+ expect(rules(tpl)).toEqual(expect.arrayContaining(['space-package-cell-key-duplicate', 'space-package-cell-key']));
113
+ });
114
+ });
115
+
116
+ describe('the Builder space opens its own built code', () => {
117
+ it('installs the cell its app opens', () => {
118
+ const def = toPackageDefinition('builder-workspace');
119
+ const surface = def.layoutConfig?.primarySurface;
120
+ expect(surface).toMatchObject({ kind: 'cell', cellKey: 'cell:builder-workspace:home' });
121
+ expect(def.cells?.map((c) => c.key)).toEqual(['home']);
122
+ expect(def.cells?.[0]).toMatchObject({ rendererType: 'frame' });
123
+ expect(def.cells?.[0].code.length).toBeGreaterThan(0);
124
+ });
125
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Space packages: the built code a template installs in its space.
3
+ *
4
+ * `scripts/build-space-packages.mjs` bundles each template's
5
+ * `spacePackage.cells[]` into `space-packages.generated.ts` (committed, like
6
+ * `templates.ts`, so every consumer of `toPackageDefinition` gets the same
7
+ * cells without a bundler). This module reads that output for the converter.
8
+ */
9
+ import type { WorkspaceYaml } from './types';
10
+ import { BUILT_SPACE_PACKAGES } from './space-packages.generated.js';
11
+
12
+ /** One built module: the declared cell plus its bundled ES module source. */
13
+ export interface BuiltSpacePackageCell {
14
+ key: string;
15
+ name: string;
16
+ source: string;
17
+ /** The ES module the frame runs (default export = a React component). */
18
+ code: string;
19
+ /**
20
+ * Every name the module imports from the host's `@synap/kit` frame module:
21
+ * the kit contract it was built against (the build refused any name the kit
22
+ * did not export).
23
+ */
24
+ kitImports: string[];
25
+ }
26
+
27
+ /**
28
+ * A template's built space package. It bundles only its own modules
29
+ * (`packages/web/src/space-packages/`): the shared layers come from the host's
30
+ * kit, whose own build record is `FRAME_HOST_MODULES_BUILD`
31
+ * (`@synap-core/web` cell-runtime).
32
+ */
33
+ export interface BuiltSpacePackage {
34
+ cells: BuiltSpacePackageCell[];
35
+ /** sha256 of every synap-app file the bundles read (repo-relative path → hex). */
36
+ inputs: Record<string, string>;
37
+ }
38
+
39
+ /**
40
+ * A space package cell on the package wire (`PackageDefinition.cells[]`, the
41
+ * pod's `PackageCellDef`): a sandboxed `frame` cell. No `externalHosts`: a
42
+ * space package is network-contained; its only data path is the host bridge.
43
+ */
44
+ export interface SpacePackageCellWire {
45
+ key: string;
46
+ name: string;
47
+ code: string;
48
+ rendererType: 'frame';
49
+ }
50
+
51
+ /**
52
+ * The cells a template's space package installs, or `undefined` when it has
53
+ * none. Throws when a declared cell was never built (or was built from another
54
+ * source): a template must never ship an entry that opens "Unknown cell".
55
+ */
56
+ export function spacePackageCells(
57
+ tpl: WorkspaceYaml,
58
+ built: Record<string, BuiltSpacePackage> = BUILT_SPACE_PACKAGES
59
+ ): SpacePackageCellWire[] | undefined {
60
+ const declared = tpl.spacePackage?.cells;
61
+ if (!declared?.length) return undefined;
62
+ const pkg = built[tpl.meta.slug];
63
+ return declared.map((cell) => {
64
+ const b = pkg?.cells.find((c) => c.key === cell.key && c.source === cell.source);
65
+ if (!b) {
66
+ throw new Error(
67
+ `Space package cell "${cell.key}" of template "${tpl.meta.slug}" is not built from ${cell.source}: ` +
68
+ 'run pnpm --filter @synap-core/workspace-templates build:space-packages'
69
+ );
70
+ }
71
+ return { key: cell.key, name: cell.name, code: b.code, rendererType: 'frame' };
72
+ });
73
+ }