@flamingo-stack/openframe-frontend-core 0.0.563 → 0.0.564-1861.5029.1

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 (138) hide show
  1. package/dist/{chunk-ZYALXBYA.js → chunk-2FYF37J3.js} +2 -2
  2. package/dist/{chunk-HORO3E6J.js → chunk-AHALU42N.js} +2 -2
  3. package/dist/{chunk-O2QXZ3CG.js → chunk-CZBZBK7M.js} +4 -4
  4. package/dist/{chunk-WNTNRQEL.cjs → chunk-DL5DXWJC.cjs} +20 -20
  5. package/dist/{chunk-WNTNRQEL.cjs.map → chunk-DL5DXWJC.cjs.map} +1 -1
  6. package/dist/{chunk-TVG2FHQX.js → chunk-E7DXYEHE.js} +2 -2
  7. package/dist/{chunk-2G65HZUS.cjs → chunk-EC6L6JMJ.cjs} +3 -3
  8. package/dist/{chunk-2G65HZUS.cjs.map → chunk-EC6L6JMJ.cjs.map} +1 -1
  9. package/dist/{chunk-HGSDDB2K.cjs → chunk-EZBTXXKD.cjs} +14 -14
  10. package/dist/{chunk-HGSDDB2K.cjs.map → chunk-EZBTXXKD.cjs.map} +1 -1
  11. package/dist/{chunk-D2C6VCH2.cjs → chunk-FN3RUX3O.cjs} +7 -7
  12. package/dist/{chunk-D2C6VCH2.cjs.map → chunk-FN3RUX3O.cjs.map} +1 -1
  13. package/dist/{chunk-J47EIBVD.js → chunk-IQJLPMM2.js} +2 -2
  14. package/dist/{chunk-IZ5NSAOY.js → chunk-KH2MXP6G.js} +2 -2
  15. package/dist/{chunk-DWOFOVKK.js → chunk-KYO5Z4NJ.js} +2 -2
  16. package/dist/{chunk-VOD7HME2.cjs → chunk-M4L5CPIC.cjs} +38 -5
  17. package/dist/chunk-M4L5CPIC.cjs.map +1 -0
  18. package/dist/{chunk-AQKAKYLA.js → chunk-MOEVW5IF.js} +37 -4
  19. package/dist/chunk-MOEVW5IF.js.map +1 -0
  20. package/dist/{chunk-EAUQ7FVQ.cjs → chunk-NQ5HGG3C.cjs} +26 -26
  21. package/dist/{chunk-EAUQ7FVQ.cjs.map → chunk-NQ5HGG3C.cjs.map} +1 -1
  22. package/dist/{chunk-WFMF7YCC.js → chunk-O22EZW7V.js} +2 -2
  23. package/dist/{chunk-4AZVFEFJ.js → chunk-O2XIWDKK.js} +5 -5
  24. package/dist/{chunk-6KBIGWCX.cjs → chunk-OKBNMR2M.cjs} +31 -31
  25. package/dist/{chunk-6KBIGWCX.cjs.map → chunk-OKBNMR2M.cjs.map} +1 -1
  26. package/dist/{chunk-ZGE45I4O.cjs → chunk-PLNDJLSK.cjs} +11 -11
  27. package/dist/{chunk-ZGE45I4O.cjs.map → chunk-PLNDJLSK.cjs.map} +1 -1
  28. package/dist/{chunk-475LZFVJ.cjs → chunk-PXZBRYIB.cjs} +39 -39
  29. package/dist/{chunk-475LZFVJ.cjs.map → chunk-PXZBRYIB.cjs.map} +1 -1
  30. package/dist/{chunk-KNGTLGTV.cjs → chunk-R5O54P4L.cjs} +5 -5
  31. package/dist/{chunk-KNGTLGTV.cjs.map → chunk-R5O54P4L.cjs.map} +1 -1
  32. package/dist/{chunk-VTRHCB5Y.js → chunk-TH4TNYFM.js} +4 -4
  33. package/dist/{chunk-A27PO3EI.js → chunk-U5XBD34S.js} +6 -6
  34. package/dist/{chunk-S7UOFXEB.cjs → chunk-URJSFWPH.cjs} +9 -9
  35. package/dist/{chunk-S7UOFXEB.cjs.map → chunk-URJSFWPH.cjs.map} +1 -1
  36. package/dist/{chunk-WPBMH6JX.js → chunk-WWNQFZ7R.js} +2 -2
  37. package/dist/{chunk-GMAA6A3V.js → chunk-XEQBE6RN.js} +2 -2
  38. package/dist/{chunk-LVM3KJJN.cjs → chunk-XL3HIB3C.cjs} +62 -62
  39. package/dist/{chunk-LVM3KJJN.cjs.map → chunk-XL3HIB3C.cjs.map} +1 -1
  40. package/dist/{chunk-VOSOLVXC.cjs → chunk-YOI3SIVL.cjs} +4 -4
  41. package/dist/{chunk-VOSOLVXC.cjs.map → chunk-YOI3SIVL.cjs.map} +1 -1
  42. package/dist/{chunk-SDCFVKFA.cjs → chunk-Z4WGVDF3.cjs} +86 -86
  43. package/dist/{chunk-SDCFVKFA.cjs.map → chunk-Z4WGVDF3.cjs.map} +1 -1
  44. package/dist/components/case-studies/index.cjs +8 -8
  45. package/dist/components/case-studies/index.js +2 -2
  46. package/dist/components/chat/entity-cards/employee-entry-card.d.ts.map +1 -1
  47. package/dist/components/chat/index.cjs +2 -2
  48. package/dist/components/chat/index.js +1 -1
  49. package/dist/components/comment-card.d.ts +11 -2
  50. package/dist/components/comment-card.d.ts.map +1 -1
  51. package/dist/components/contact/index.cjs +3 -3
  52. package/dist/components/contact/index.js +2 -2
  53. package/dist/components/docs/index.cjs +5 -5
  54. package/dist/components/docs/index.js +4 -4
  55. package/dist/components/embeds/index.cjs +3 -3
  56. package/dist/components/embeds/index.js +2 -2
  57. package/dist/components/faq/index.cjs +3 -3
  58. package/dist/components/faq/index.js +2 -2
  59. package/dist/components/features/clickup-tasks-manager.d.ts +6 -1
  60. package/dist/components/features/clickup-tasks-manager.d.ts.map +1 -1
  61. package/dist/components/features/index.cjs +2 -2
  62. package/dist/components/features/index.js +1 -1
  63. package/dist/components/form.d.ts +1 -1
  64. package/dist/components/form.d.ts.map +1 -1
  65. package/dist/components/help-center-pages/index.cjs +22 -22
  66. package/dist/components/help-center-pages/index.js +13 -13
  67. package/dist/components/index.cjs +210 -197
  68. package/dist/components/index.cjs.map +1 -1
  69. package/dist/components/index.js +90 -77
  70. package/dist/components/index.js.map +1 -1
  71. package/dist/components/meeting-scheduler/index.cjs +30 -30
  72. package/dist/components/meeting-scheduler/index.js +3 -3
  73. package/dist/components/navigation/index.cjs +2 -2
  74. package/dist/components/navigation/index.js +1 -1
  75. package/dist/components/onboarding-guides/hooks/use-onboarding-guides.d.ts +3 -3
  76. package/dist/components/onboarding-guides/hooks/use-onboarding-guides.d.ts.map +1 -1
  77. package/dist/components/onboarding-guides/index.cjs +5 -5
  78. package/dist/components/onboarding-guides/index.js +4 -4
  79. package/dist/components/related-content/index.cjs +3 -3
  80. package/dist/components/related-content/index.js +2 -2
  81. package/dist/components/tickets/index.cjs +6 -6
  82. package/dist/components/tickets/index.js +5 -5
  83. package/dist/components/ui/index.cjs +2 -2
  84. package/dist/components/ui/index.js +1 -1
  85. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -1
  86. package/dist/hooks/use-meeting-booking.d.ts +1 -1
  87. package/dist/index.cjs +16 -2
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.js +15 -1
  90. package/dist/types/department.d.ts +36 -0
  91. package/dist/types/department.d.ts.map +1 -0
  92. package/dist/types/design-doc.d.ts +242 -0
  93. package/dist/types/design-doc.d.ts.map +1 -0
  94. package/dist/types/employee.d.ts +6 -2
  95. package/dist/types/employee.d.ts.map +1 -1
  96. package/dist/types/index.cjs +16 -0
  97. package/dist/types/index.cjs.map +1 -1
  98. package/dist/types/index.d.ts +2 -0
  99. package/dist/types/index.d.ts.map +1 -1
  100. package/dist/types/index.js +10 -1
  101. package/dist/types/index.js.map +1 -1
  102. package/dist/types/marketing.d.ts +1 -1
  103. package/dist/types/marketing.d.ts.map +1 -1
  104. package/dist/types/profile.d.ts +2 -1
  105. package/dist/types/profile.d.ts.map +1 -1
  106. package/dist/types/team.d.ts +7 -1
  107. package/dist/types/team.d.ts.map +1 -1
  108. package/dist/types/user.d.ts +3 -1
  109. package/dist/types/user.d.ts.map +1 -1
  110. package/package.json +1 -1
  111. package/src/components/chat/entity-cards/employee-entry-card.tsx +6 -0
  112. package/src/components/comment-card.tsx +32 -3
  113. package/src/components/features/clickup-tasks-manager.tsx +8 -1
  114. package/src/components/ui/markdown/rich/__tests__/shortcodes.test.ts +47 -0
  115. package/src/components/ui/markdown/rich/shortcodes.ts +11 -0
  116. package/src/types/department.ts +39 -0
  117. package/src/types/design-doc.ts +284 -0
  118. package/src/types/employee.ts +7 -2
  119. package/src/types/index.ts +2 -0
  120. package/src/types/marketing.ts +1 -0
  121. package/src/types/profile.ts +3 -1
  122. package/src/types/team.ts +9 -1
  123. package/src/types/user.ts +4 -1
  124. package/dist/chunk-AQKAKYLA.js.map +0 -1
  125. package/dist/chunk-VOD7HME2.cjs.map +0 -1
  126. /package/dist/{chunk-ZYALXBYA.js.map → chunk-2FYF37J3.js.map} +0 -0
  127. /package/dist/{chunk-HORO3E6J.js.map → chunk-AHALU42N.js.map} +0 -0
  128. /package/dist/{chunk-O2QXZ3CG.js.map → chunk-CZBZBK7M.js.map} +0 -0
  129. /package/dist/{chunk-TVG2FHQX.js.map → chunk-E7DXYEHE.js.map} +0 -0
  130. /package/dist/{chunk-J47EIBVD.js.map → chunk-IQJLPMM2.js.map} +0 -0
  131. /package/dist/{chunk-IZ5NSAOY.js.map → chunk-KH2MXP6G.js.map} +0 -0
  132. /package/dist/{chunk-DWOFOVKK.js.map → chunk-KYO5Z4NJ.js.map} +0 -0
  133. /package/dist/{chunk-WFMF7YCC.js.map → chunk-O22EZW7V.js.map} +0 -0
  134. /package/dist/{chunk-4AZVFEFJ.js.map → chunk-O2XIWDKK.js.map} +0 -0
  135. /package/dist/{chunk-VTRHCB5Y.js.map → chunk-TH4TNYFM.js.map} +0 -0
  136. /package/dist/{chunk-A27PO3EI.js.map → chunk-U5XBD34S.js.map} +0 -0
  137. /package/dist/{chunk-WPBMH6JX.js.map → chunk-WWNQFZ7R.js.map} +0 -0
  138. /package/dist/{chunk-GMAA6A3V.js.map → chunk-XEQBE6RN.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Types related to users
3
3
  */
4
+ import type { DepartmentRef } from './department';
4
5
  /**
5
6
  * User profile
6
7
  */
@@ -13,7 +14,8 @@ export type UserProfile = {
13
14
  company: string | null;
14
15
  bio: string | null;
15
16
  employee_id: string | null;
16
- department: string | null;
17
+ department_id: string | null;
18
+ department: DepartmentRef | null;
17
19
  role: 'user' | 'super_admin';
18
20
  msp_id: string | null;
19
21
  created_at: string;
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE,MAAM,GAAG,aAAa,CAAA;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,aAAa,EAAE,QAAQ,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC/H,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;CACtC,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;IAClC,mBAAmB,EAAE,OAAO,CAAA;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,UAAU,EAAE,aAAa,GAAG,IAAI,CAAA;IAChC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAA;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,aAAa,EAAE,QAAQ,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC/H,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;CACtC,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;IAClC,mBAAmB,EAAE,OAAO,CAAA;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.563",
3
+ "version": "0.0.564-1861.5029.1",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,6 +38,12 @@ const STATUS_BADGE_CLASS: Record<string, string> = {
38
38
  published: 'bg-ods-success-secondary text-ods-success',
39
39
  draft: 'bg-ods-warning-secondary text-ods-warning',
40
40
  archived: 'bg-ods-border text-ods-text-secondary',
41
+ // Design-doc lifecycle (product-hub) — same ODS pairs, no new palette.
42
+ in_review: 'bg-ods-warning-secondary text-ods-warning',
43
+ approved: 'bg-ods-success-secondary text-ods-success',
44
+ building: 'bg-ods-success-secondary text-ods-success',
45
+ shipped: 'bg-ods-success-secondary text-ods-success',
46
+ abandoned: 'bg-ods-border text-ods-text-secondary',
41
47
  }
42
48
 
43
49
  /** Shared badge treatment so an entity's own badges (discipline, level) sit
@@ -37,7 +37,16 @@ interface CommentCardProps {
37
37
  onDeleteComment?: (commentId: string) => void
38
38
  showVendorInfo?: boolean
39
39
  compact?: boolean
40
- context: 'profile' | 'vendor'
40
+ /**
41
+ * `profile` / `vendor` = the OpenMSP comment sections (fixed-height list tiles).
42
+ * `design_doc` = the product-hub design-doc thread: full-height body, no vendor
43
+ * chrome, deletability decided by the caller via `comment.canDelete`.
44
+ */
45
+ context: 'profile' | 'vendor' | 'design_doc'
46
+ /** Rendered under the body — badges + per-surface actions (design-doc threads). */
47
+ footer?: React.ReactNode
48
+ /** Rendered after the card — nested replies + reply composer (design-doc threads). */
49
+ children?: React.ReactNode
41
50
  }
42
51
 
43
52
  export function CommentCard({
@@ -46,7 +55,9 @@ export function CommentCard({
46
55
  onDeleteComment,
47
56
  showVendorInfo = true,
48
57
  compact = false,
49
- context = 'profile'
58
+ context = 'profile',
59
+ footer,
60
+ children
50
61
  }: CommentCardProps) {
51
62
  const { user: currentUser } = useAuth()
52
63
 
@@ -69,13 +80,21 @@ export function CommentCard({
69
80
  if (context === 'vendor' && comment.user) {
70
81
  return currentUser.id === comment.user.id
71
82
  }
83
+
84
+ // Design-doc threads: ownership (own-or-DRI) is computed by the caller with the
85
+ // shared gate rule and handed in as `canDelete`.
86
+ if (context === 'design_doc') {
87
+ return comment.canDelete === true
88
+ }
72
89
 
73
90
  return false
74
91
  }
75
92
 
76
93
  const showDeleteButton = canUserDeleteComment()
94
+ const isThread = context === 'design_doc'
77
95
 
78
96
  return (
97
+ <>
79
98
  <div className="bg-ods-card border border-ods-border rounded-lg p-4 hover:border-ods-accent transition-all group overflow-hidden w-full max-w-full box-border" style={{ maxWidth: '100%', wordBreak: 'break-word' }}>
80
99
  {/* Comment Header */}
81
100
  <div className="flex flex-col gap-3 mb-3 min-[420px]:flex-row min-[420px]:items-center min-[420px]:justify-between min-[420px]:gap-2 w-full">
@@ -94,7 +113,7 @@ export function CommentCard({
94
113
  {formatActivityTime(comment.createdAt)}
95
114
  </span>
96
115
  </>
97
- ) : context === 'vendor' && comment.user ? (
116
+ ) : (context === 'vendor' || isThread) && comment.user ? (
98
117
  <UserSummary
99
118
  name={comment.user.name}
100
119
  email=""
@@ -152,6 +171,12 @@ export function CommentCard({
152
171
  </h4>
153
172
  </div>
154
173
  )}
174
+ {isThread ? (
175
+ // Thread bodies are read in full — no fixed height, no clamp.
176
+ <p className="text-h4 text-ods-text-primary whitespace-pre-wrap" style={{ wordBreak: 'break-word', overflowWrap: 'break-word', maxWidth: '100%' }}>
177
+ {comment.content}
178
+ </p>
179
+ ) : (
155
180
  <div className={compact ? "h-[60px] flex items-center" : "h-[72px] flex items-center"}>
156
181
  <p className="text-h4 text-ods-text-primary line-clamp-3" style={{
157
182
  wordBreak: 'break-word',
@@ -166,7 +191,11 @@ export function CommentCard({
166
191
  {comment.content}
167
192
  </p>
168
193
  </div>
194
+ )}
195
+ {footer ? <div className="pt-2 flex flex-wrap items-center gap-2">{footer}</div> : null}
169
196
  </div>
170
197
  </div>
198
+ {children}
199
+ </>
171
200
  )
172
201
  }
@@ -14,13 +14,19 @@ interface ClickUpTasksManagerProps {
14
14
  onChange: (tasks: ClickUpTaskLink[]) => void;
15
15
  title?: ReactNode; // Support string or ReactNode for badge integration
16
16
  className?: string;
17
+ /**
18
+ * Per-row label renderer (passthrough to ArrayEntryManager) — lets a host
19
+ * hydrate the bare task id with the mirror's title/status row.
20
+ */
21
+ renderLabel?: (item: ClickUpTaskLink, index: number) => ReactNode;
17
22
  }
18
23
 
19
24
  export function ClickUpTasksManager({
20
25
  tasks,
21
26
  onChange,
22
27
  title = 'ClickUp Roadmap Tasks', // Default title
23
- className = ''
28
+ className = '',
29
+ renderLabel
24
30
  }: ClickUpTasksManagerProps) {
25
31
  return (
26
32
  <ArrayEntryManager
@@ -33,6 +39,7 @@ export function ClickUpTasksManager({
33
39
  addButtonText="Add Task"
34
40
  icon={<Lightbulb className="w-5 h-5 text-ods-text-secondary" />}
35
41
  className={className}
42
+ renderLabel={renderLabel}
36
43
  />
37
44
  );
38
45
  }
@@ -56,3 +56,50 @@ describe('processShortcodes script stripping', () => {
56
56
  expect(output).toContain('<script>');
57
57
  });
58
58
  });
59
+
60
+ /**
61
+ * Design-doc embeds (R11): the spec's `{{figma:FILE_KEY[:NODE_ID]}}` grammar
62
+ * is rewritten onto the canonical `{{figma:URL}}` rule, and the two Claude
63
+ * shortcodes alias the OG link card because claude.ai serves
64
+ * `frame-ancestors 'self'` (it cannot be framed — verified 2026-08-23).
65
+ */
66
+ describe('processShortcodes design-doc embeds', () => {
67
+ it('rewrites {{figma:KEY:NODE}} to the canonical design URL and renders the figma embed', () => {
68
+ const output = processShortcodes('{{figma:aB3xK9:12-405}}');
69
+ expect(output).toContain('class="figma-embed"');
70
+ expect(output).toContain('data-figma-url="https://www.figma.com/design/aB3xK9?node-id=12-405"');
71
+ });
72
+
73
+ it('accepts a colon-separated node id and a bare key', () => {
74
+ expect(processShortcodes('{{figma:aB3xK9:12:405}}')).toContain('node-id=12-405');
75
+ expect(processShortcodes('{{figma:aB3xK9}}')).toContain('data-figma-url="https://www.figma.com/design/aB3xK9"');
76
+ });
77
+
78
+ it('leaves the existing {{figma:URL}} grammar untouched', () => {
79
+ const output = processShortcodes('{{figma:https://www.figma.com/proto/aB3xK9/Flows?node-id=1-2}}');
80
+ expect(output).toContain('data-figma-url="https://www.figma.com/proto/aB3xK9/Flows?node-id=1-2"');
81
+ });
82
+
83
+ it('renders {{claude-artifact:URL}} and {{claude-design:URL}} as link-preview cards, never iframes', () => {
84
+ const artifact = processShortcodes('{{claude-artifact:https://claude.ai/public/artifacts/abc}}');
85
+ const design = processShortcodes('{{claude-design:https://claude.ai/design/xyz}}');
86
+ for (const out of [artifact, design]) {
87
+ expect(out).toContain('class="link-preview"');
88
+ expect(out).not.toContain('<iframe');
89
+ }
90
+ expect(artifact).toContain('data-url="https://claude.ai/public/artifacts/abc"');
91
+ expect(design).toContain('data-url="https://claude.ai/design/xyz"');
92
+ });
93
+
94
+ it('attribute-escapes the claude URL like every other shortcode', () => {
95
+ const output = processShortcodes('{{claude-artifact:https://claude.ai/a?x="1"}}');
96
+ expect(output).toContain('data-url="https://claude.ai/a?x=&quot;1&quot;"');
97
+ expect(output).not.toContain('x="1"');
98
+ });
99
+
100
+ it('is rewritten inside a code fence exactly like {{link:}} is (parity, not fence-safety)', () => {
101
+ const link = processShortcodes('```\n{{link:https://example.com}}\n```');
102
+ const claude = processShortcodes('```\n{{claude-artifact:https://claude.ai/x}}\n```');
103
+ expect(claude.includes('link-preview')).toBe(link.includes('link-preview'));
104
+ });
105
+ });
@@ -55,6 +55,12 @@ export const processShortcodes = (content: string): string => {
55
55
  : `https://twitter.com/twitter/status/${tweetInput}`;
56
56
  return `\n\n<div class="tweet-embed" data-tweet-url="${escapeAttr(tweetUrl)}"></div>\n\n`;
57
57
  })
58
+ // Figma by file key (spec grammar): {{figma:FILE_KEY[:NODE_ID]}} → rewritten to the
59
+ // canonical URL form so the {{figma:URL}} rule below owns the rendering.
60
+ .replace(/\{\{figma:(?!https?:)([A-Za-z0-9]+)(?::([0-9]+[-:][0-9]+))?\}\}/g, (match, key, node) => {
61
+ const nodeParam = node ? `?node-id=${String(node).replace(':', '-')}` : '';
62
+ return `{{figma:https://www.figma.com/design/${key}${nodeParam}}}`;
63
+ })
58
64
  // Figma embeds: {{figma:URL}}
59
65
  .replace(/\{\{figma:([^}]+)\}\}/g, (match, url) => {
60
66
  return `\n\n<div class="figma-embed" data-figma-url="${escapeAttr(url.trim())}"></div>\n\n`;
@@ -63,6 +69,11 @@ export const processShortcodes = (content: string): string => {
63
69
  .replace(/\{\{linkedin:([^}]+)\}\}/g, (match, url) => {
64
70
  return `\n\n<div class="linkedin-embed" data-post-url="${escapeAttr(url.trim())}"></div>\n\n`;
65
71
  })
72
+ // Claude artifact / Claude Design: claude.ai serves `frame-ancestors 'self'` (cannot be
73
+ // framed by anyone), so both render as the OG link card — never an iframe.
74
+ .replace(/\{\{claude-(?:artifact|design):([^}]+)\}\}/g, (match, url) => {
75
+ return `\n\n<div class="link-preview" data-url="${escapeAttr(url.trim())}"></div>\n\n`;
76
+ })
66
77
  // Link previews: {{link:URL}}
67
78
  .replace(/\{\{link:([^}]+)\}\}/g, (match, url) => {
68
79
  return `\n\n<div class="link-preview" data-url="${escapeAttr(url.trim())}"></div>\n\n`;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Departments — THE single source of truth for the org's department
3
+ * vocabulary.
4
+ *
5
+ * The `departments` table is populated from Google Workspace (the org chart)
6
+ * by the hub's Google sync, which auto-creates a row the first time it sees a
7
+ * new department name. `profiles.department_id` is an FK to it; there is no
8
+ * free-text department column and no enum anywhere in code — every consumer
9
+ * reads the row (or the embedded `DepartmentRef`) and every picker lists the
10
+ * ACTIVE rows ordered by `display_order`.
11
+ *
12
+ * Hub owner module: `lib/data/department-utils.ts`.
13
+ */
14
+
15
+ export interface Department {
16
+ id: string
17
+ name: string
18
+ slug: string
19
+ description?: string | null
20
+ parent_department_id?: string | null
21
+ display_order: number
22
+ is_active: boolean
23
+ }
24
+
25
+ /** The embedded shape a reader gets through the `department:departments(…)` PostgREST embed. */
26
+ export type DepartmentRef = Pick<Department, 'id' | 'name' | 'slug'> & {
27
+ display_order?: number
28
+ }
29
+
30
+ /** `GET /api/admin/departments?counts=…` row — a department plus how many profiles carry it. */
31
+ export interface DepartmentSummary {
32
+ id: string
33
+ name: string
34
+ slug: string
35
+ count: number
36
+ }
37
+
38
+ /** Population a department count is taken over. */
39
+ export type DepartmentCountPopulation = 'directory' | 'scored'
@@ -0,0 +1,284 @@
1
+ // Design Doc Types (product-hub)
2
+ // Mirrors product-release.ts — entity + child tables + create/update/filter shapes.
3
+ //
4
+ // Participant sections are keyed by the employee DEPARTMENT (FK to the ONE
5
+ // `departments` table — see department.ts); there is no function enum.
6
+ // Completion-gate math and status transitions live in the hub's
7
+ // `lib/utils/design-doc-gate.ts` (isomorphic, shared by server + client).
8
+
9
+ import type { EntityAuthor } from './entity-author'
10
+ import type { DeliveryItem } from './delivery'
11
+ import type { DepartmentRef } from './department'
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Value vocabularies (string unions + `as const` arrays; badge colors and
15
+ // option labels are declared ONCE in the hub gate module, not here)
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const DESIGN_DOC_STATUSES = ['draft', 'in_review', 'approved', 'building', 'shipped', 'abandoned'] as const
19
+ export type DesignDocStatus = (typeof DESIGN_DOC_STATUSES)[number]
20
+
21
+ export const DESIGN_DOC_TIERS = ['S', 'M', 'L'] as const
22
+ export type DesignDocTier = (typeof DESIGN_DOC_TIERS)[number]
23
+
24
+ export const DESIGN_DOC_PARTICIPANT_STATUSES = ['pending', 'in_progress', 'completed', 'blocked'] as const
25
+ export type DesignDocParticipantStatus = (typeof DESIGN_DOC_PARTICIPANT_STATUSES)[number]
26
+
27
+ export const DESIGN_DOC_COMMENT_TYPES = ['blocking', 'suggestion', 'question', 'note'] as const
28
+ export type DesignDocCommentType = (typeof DESIGN_DOC_COMMENT_TYPES)[number]
29
+
30
+ export const DESIGN_DOC_COMMENT_STATUSES = ['open', 'resolved', 'declined'] as const
31
+ export type DesignDocCommentStatus = (typeof DESIGN_DOC_COMMENT_STATUSES)[number]
32
+
33
+ export const DESIGN_DOC_LINK_TYPES = ['roadmap_task', 'figma', 'claude_artifact', 'claude_design', 'prototype', 'brief', 'other'] as const
34
+ export type DesignDocLinkType = (typeof DESIGN_DOC_LINK_TYPES)[number]
35
+
36
+ export const DESIGN_DOC_MEDIA_TYPES = ['image', 'video', 'screenshot', 'demo'] as const
37
+ export type DesignDocMediaType = (typeof DESIGN_DOC_MEDIA_TYPES)[number]
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Rows (as returned by the hub DAL — hydrated)
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /** Minimal profile identity the DAL embeds on participants / comment authors. */
44
+ export interface DesignDocPerson {
45
+ id: string
46
+ full_name: string | null
47
+ avatar_url: string | null
48
+ job_title?: string | null
49
+ /** Present on the DRI/detail payload only — never on list payloads. */
50
+ email?: string | null
51
+ department_id?: string | null
52
+ department?: DepartmentRef | null
53
+ }
54
+
55
+ export interface DesignDocClickUpTask {
56
+ id: number
57
+ doc_id: number
58
+ participant_id: number | null
59
+ clickup_task_id: string
60
+ display_order: number | null
61
+ created_at: string
62
+ /** Hydrated from the `clickup_tasks` mirror (never the live API). `undefined` = not in the mirror. */
63
+ task?: DeliveryItem
64
+ }
65
+
66
+ export interface DesignDocLink {
67
+ id: number
68
+ doc_id: number
69
+ link_type: DesignDocLinkType
70
+ url: string | null
71
+ clickup_task_id: string | null
72
+ title: string | null
73
+ display_order: number | null
74
+ created_at: string
75
+ /** `roadmap_task` links are hydrated from the mirror too. */
76
+ task?: DeliveryItem
77
+ }
78
+
79
+ export interface DesignDocMedia {
80
+ id: number
81
+ doc_id: number
82
+ participant_id: number | null
83
+ media_type: DesignDocMediaType
84
+ media_url: string
85
+ title: string | null
86
+ description: string | null
87
+ display_order: number | null
88
+ created_at: string
89
+ created_by: string | null
90
+ }
91
+
92
+ /**
93
+ * A comment. ROOT comments (`parent_comment_id === null`) carry `comment_type`
94
+ * + `status` and gate approval when `blocking` + `open`; REPLIES carry neither
95
+ * (single-level threads — a reply to a reply is re-parented to the root).
96
+ */
97
+ export interface DesignDocComment {
98
+ id: number
99
+ doc_id: number
100
+ participant_id: number | null
101
+ parent_comment_id: number | null
102
+ author_id: string | null
103
+ comment_type: DesignDocCommentType | null
104
+ title: string | null
105
+ body: string
106
+ status: DesignDocCommentStatus | null
107
+ resolved_at: string | null
108
+ created_at: string
109
+ author?: DesignDocPerson | null
110
+ /** Threaded by the DAL; always `[]` on replies. */
111
+ replies: DesignDocComment[]
112
+ }
113
+
114
+ export interface DesignDocParticipant {
115
+ id: number
116
+ doc_id: number
117
+ department_id: string
118
+ department: DepartmentRef
119
+ assignee_id: string | null
120
+ assignee?: DesignDocPerson | null
121
+ status: DesignDocParticipantStatus
122
+ content: string | null
123
+ completed_at: string | null
124
+ display_order: number | null
125
+ created_at: string
126
+ updated_at: string
127
+ tasks: DesignDocClickUpTask[]
128
+ media: DesignDocMedia[]
129
+ /** Section-scoped root comments (threaded). */
130
+ comments: DesignDocComment[]
131
+ }
132
+
133
+ export interface DesignDocCompletion {
134
+ total: number
135
+ completed: number
136
+ blocked: number
137
+ openBlocking: number
138
+ isComplete: boolean
139
+ waitingOn: Array<{
140
+ participantId: number
141
+ department: DepartmentRef
142
+ assigneeName: string | null
143
+ status: DesignDocParticipantStatus
144
+ }>
145
+ }
146
+
147
+ /** R13 — what the signed-in viewer still owes on one doc. */
148
+ export interface DesignDocMyOpenWork {
149
+ /** One person may own several department sections on the same doc. */
150
+ participants: Array<{ id: number; department: DepartmentRef; status: DesignDocParticipantStatus }>
151
+ openTasks: DeliveryItem[]
152
+ }
153
+
154
+ export interface DesignDoc {
155
+ id: number
156
+ title: string
157
+ slug: string
158
+ tier: DesignDocTier
159
+ summary: string | null
160
+ content: string | null
161
+ status: DesignDocStatus
162
+ author_id: string | null
163
+ approved_at: string | null
164
+ created_at: string
165
+ updated_at: string
166
+ /** DRI, hydrated via the shared author hydrator. */
167
+ author?: EntityAuthor
168
+ participants: DesignDocParticipant[]
169
+ links: DesignDocLink[]
170
+ /** Doc-level comments only (participant-scoped comments ride on their participant). */
171
+ comments: DesignDocComment[]
172
+ completion: DesignDocCompletion
173
+ my_open_work?: DesignDocMyOpenWork
174
+ }
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Write shapes
178
+ // ---------------------------------------------------------------------------
179
+
180
+ export interface DesignDocLinkInput {
181
+ link_type: DesignDocLinkType
182
+ url?: string | null
183
+ clickup_task_id?: string | null
184
+ title?: string | null
185
+ display_order?: number
186
+ }
187
+
188
+ export interface DesignDocClickUpTaskInput {
189
+ clickup_task_id: string
190
+ display_order?: number
191
+ }
192
+
193
+ export interface DesignDocMediaInput {
194
+ media_type: DesignDocMediaType
195
+ media_url: string
196
+ title?: string | null
197
+ description?: string | null
198
+ display_order?: number
199
+ }
200
+
201
+ export interface CreateDesignDocParticipantInput {
202
+ /** Defaults to the assignee's profile department when omitted. */
203
+ department_id?: string
204
+ assignee_id: string
205
+ }
206
+
207
+ /** Wizard payload (POST /api/admin/design-docs). */
208
+ export interface CreateDesignDocData {
209
+ title: string
210
+ slug: string
211
+ tier: DesignDocTier
212
+ summary?: string | null
213
+ /** DRI; defaults to the caller. */
214
+ author_id?: string | null
215
+ participants: CreateDesignDocParticipantInput[]
216
+ links: DesignDocLinkInput[]
217
+ }
218
+
219
+ /**
220
+ * Doc-level allowlist (PUT). `status` and `participants` are NOT here — status
221
+ * only moves through PATCH (the gate), participants only through their routes.
222
+ */
223
+ export interface UpdateDesignDocData {
224
+ title?: string
225
+ slug?: string
226
+ tier?: DesignDocTier
227
+ summary?: string | null
228
+ content?: string | null
229
+ author_id?: string | null
230
+ links?: DesignDocLinkInput[]
231
+ /** OCC token — the `updated_at` the editor seeded from; mismatch → 409. */
232
+ expected_updated_at?: string
233
+ }
234
+
235
+ export interface UpdateDesignDocParticipantData {
236
+ content?: string | null
237
+ status?: DesignDocParticipantStatus
238
+ assignee_id?: string | null
239
+ department_id?: string
240
+ clickup_tasks?: DesignDocClickUpTaskInput[]
241
+ media?: DesignDocMediaInput[]
242
+ expected_updated_at?: string
243
+ }
244
+
245
+ export interface AddDesignDocParticipantData {
246
+ /** Defaults to the assignee's profile department when omitted. */
247
+ department_id?: string
248
+ assignee_id: string
249
+ }
250
+
251
+ export interface AddDesignDocCommentData {
252
+ participant_id?: number | null
253
+ /** Set for a reply; the DAL re-parents replies-of-replies to the root. */
254
+ parent_comment_id?: number | null
255
+ /** Required on root comments, ignored on replies. */
256
+ comment_type?: DesignDocCommentType
257
+ title?: string | null
258
+ body: string
259
+ }
260
+
261
+ export interface DesignDocFilters {
262
+ status?: string
263
+ search?: string
264
+ mine?: 'open'
265
+ limit?: number
266
+ offset?: number
267
+ }
268
+
269
+ export interface DesignDocListResponse {
270
+ data: DesignDoc[]
271
+ count: number
272
+ /** Server-computed status facet counts (how-i-work feed convention). */
273
+ facets: { status: Record<string, number> }
274
+ }
275
+
276
+ export interface DesignDocStats {
277
+ total: number
278
+ draft: number
279
+ in_review: number
280
+ approved: number
281
+ building: number
282
+ waiting_on_me_docs: number
283
+ waiting_on_me_tasks: number
284
+ }
@@ -1,7 +1,11 @@
1
+ import type { DepartmentRef } from './department';
2
+
1
3
  export interface Employee {
2
4
  id: string;
3
5
  full_name: string;
4
- department: string;
6
+ /** FK to the ONE `departments` table; `department` is the embedded row. */
7
+ department_id: string | null;
8
+ department: DepartmentRef | null;
5
9
  avatar_url: string;
6
10
  role: string;
7
11
  job_title: string;
@@ -16,7 +20,8 @@ export interface EmployeeResponse {
16
20
 
17
21
  export interface EmployeeProfileData {
18
22
  full_name: string;
19
- department: string;
23
+ department_id: string | null;
24
+ department: DepartmentRef | null;
20
25
  avatar_url: string;
21
26
  role: string;
22
27
  job_title: string;
@@ -28,11 +28,13 @@ export * from './customer-interview-ai.types'
28
28
  export * from './luma'
29
29
  export * from './product-release'
30
30
  export * from './delivery'
31
+ export * from './design-doc'
31
32
  export * from './vendor'
32
33
  export * from './vendor-links'
33
34
  export * from './video-processing'
34
35
 
35
36
  // User and profile types
37
+ export * from './department'
36
38
  export * from './employee'
37
39
  export * from './profile'
38
40
  export * from './team'
@@ -350,6 +350,7 @@ export type ContentSourceType =
350
350
  | 'what_i_shipped' // What I Shipped employee check-ins (lives on people-hub)
351
351
  | 'how_i_work' // How I Work employee AI-workflow sessions (lives on people-hub)
352
352
  | 'faq' // FAQ Q&A pair (single-page /faqs index; deep-link by category anchor)
353
+ | 'design_doc' // Internal design docs (product-hub admin; preview route only, no public page)
353
354
  | 'from_scratch';
354
355
 
355
356
  export type URLInjectionPreference = 'none' | 'in_post' | 'as_comment';
@@ -1,5 +1,7 @@
1
1
  // Profile system TypeScript interfaces for OpenMSP
2
2
 
3
+ import type { DepartmentRef } from './department'
4
+
3
5
  export interface ProfileResponse {
4
6
  id: string;
5
7
  name: string;
@@ -14,7 +16,7 @@ export interface ProfileResponse {
14
16
  jobTitle: string | null;
15
17
  isCommunityMember: boolean;
16
18
  lastSignIn: string | null;
17
- department: string | null;
19
+ department: DepartmentRef | null;
18
20
  employeeId: string | null;
19
21
  }
20
22
 
package/src/types/team.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { DepartmentRef } from './department';
2
+
1
3
  export interface TeamMember {
2
4
  id: string;
3
5
  employee_id: string;
@@ -9,7 +11,9 @@ export interface TeamMember {
9
11
  phone?: string;
10
12
  role?: string;
11
13
  title?: string;
14
+ /** Display name of the department (presentational shape); `department_id` is the FK. */
12
15
  department?: string;
16
+ department_id?: string | null;
13
17
  team?: string;
14
18
  manager_id?: string;
15
19
  profile_image_url?: string;
@@ -33,7 +37,11 @@ export interface TeamMember {
33
37
 
34
38
  export interface TeamSection {
35
39
  title: string;
40
+ /** Department display name. */
36
41
  department: string;
42
+ department_id: string | null;
43
+ /** `departments.display_order` — sections sort by it (no name sentinels). */
44
+ display_order: number;
37
45
  description: string;
38
46
  members: TeamMember[];
39
47
  }
@@ -45,5 +53,5 @@ export interface TeamData {
45
53
 
46
54
  export interface GetTeamOptions {
47
55
  includeInactive?: boolean;
48
- department?: string;
56
+ departmentId?: string;
49
57
  }