@flamingo-stack/openframe-frontend-core 0.0.436 → 0.0.437

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 (99) hide show
  1. package/dist/{chunk-32V2VHPE.cjs → chunk-33D5O7AD.cjs} +26 -26
  2. package/dist/{chunk-32V2VHPE.cjs.map → chunk-33D5O7AD.cjs.map} +1 -1
  3. package/dist/{chunk-FSOKLOXF.js → chunk-6HROGXG6.js} +2 -2
  4. package/dist/{chunk-E6NRV33G.cjs → chunk-7EO6RAHH.cjs} +11 -11
  5. package/dist/{chunk-E6NRV33G.cjs.map → chunk-7EO6RAHH.cjs.map} +1 -1
  6. package/dist/{chunk-2WXQFYNC.js → chunk-7NULUAOH.js} +2 -2
  7. package/dist/{chunk-NRIJNBRG.cjs → chunk-7Z26YAR3.cjs} +26 -26
  8. package/dist/{chunk-NRIJNBRG.cjs.map → chunk-7Z26YAR3.cjs.map} +1 -1
  9. package/dist/{chunk-2QJNX32P.cjs → chunk-CU5PG6Z2.cjs} +5 -5
  10. package/dist/{chunk-2QJNX32P.cjs.map → chunk-CU5PG6Z2.cjs.map} +1 -1
  11. package/dist/{chunk-LYK6D7GU.js → chunk-CWQQV4YF.js} +2 -2
  12. package/dist/{chunk-WXNE2BF4.cjs → chunk-EKPUC5NC.cjs} +37 -37
  13. package/dist/{chunk-WXNE2BF4.cjs.map → chunk-EKPUC5NC.cjs.map} +1 -1
  14. package/dist/{chunk-3OPSNUPJ.js → chunk-F24JBANX.js} +2 -2
  15. package/dist/{chunk-PXTDEKAY.cjs → chunk-FF4SI6PW.cjs} +7 -7
  16. package/dist/{chunk-PXTDEKAY.cjs.map → chunk-FF4SI6PW.cjs.map} +1 -1
  17. package/dist/{chunk-MWIYAVFT.js → chunk-GN54QCPI.js} +4 -4
  18. package/dist/{chunk-ZH2OEMQJ.js → chunk-HL62CQJC.js} +34 -1
  19. package/dist/chunk-HL62CQJC.js.map +1 -0
  20. package/dist/{chunk-KVXB3FOB.cjs → chunk-IVIRESMD.cjs} +62 -62
  21. package/dist/{chunk-KVXB3FOB.cjs.map → chunk-IVIRESMD.cjs.map} +1 -1
  22. package/dist/{chunk-AUSUEKXY.cjs → chunk-JEPZKBV5.cjs} +31 -31
  23. package/dist/{chunk-AUSUEKXY.cjs.map → chunk-JEPZKBV5.cjs.map} +1 -1
  24. package/dist/{chunk-P5EN67AU.cjs → chunk-LAZYT543.cjs} +59 -59
  25. package/dist/{chunk-P5EN67AU.cjs.map → chunk-LAZYT543.cjs.map} +1 -1
  26. package/dist/{chunk-32BSKGEI.js → chunk-M6RZRURG.js} +2 -2
  27. package/dist/{chunk-DVYA3BDU.js → chunk-O5TONTLW.js} +4 -4
  28. package/dist/{chunk-E77Z2P3K.js → chunk-OY2LB5VP.js} +4 -4
  29. package/dist/{chunk-GQHXJ37C.js → chunk-PMANI6KY.js} +2 -2
  30. package/dist/{chunk-NGBO5Z5O.cjs → chunk-PWQNLW4I.cjs} +15 -15
  31. package/dist/{chunk-NGBO5Z5O.cjs.map → chunk-PWQNLW4I.cjs.map} +1 -1
  32. package/dist/{chunk-BMEA5NPX.js → chunk-QAIGNMPP.js} +4 -4
  33. package/dist/{chunk-NFU37EP6.js → chunk-U5OADJNL.js} +2 -2
  34. package/dist/{chunk-IWYRSGJU.cjs → chunk-UXYKFAZS.cjs} +9 -9
  35. package/dist/{chunk-IWYRSGJU.cjs.map → chunk-UXYKFAZS.cjs.map} +1 -1
  36. package/dist/{chunk-3V5UQ5TU.cjs → chunk-YQOIYNBJ.cjs} +35 -2
  37. package/dist/chunk-YQOIYNBJ.cjs.map +1 -0
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/index.cjs +2 -2
  41. package/dist/components/chat/index.js +1 -1
  42. package/dist/components/contact/index.cjs +3 -3
  43. package/dist/components/contact/index.js +2 -2
  44. package/dist/components/docs/index.cjs +5 -5
  45. package/dist/components/docs/index.js +4 -4
  46. package/dist/components/embeds/index.cjs +3 -3
  47. package/dist/components/embeds/index.js +2 -2
  48. package/dist/components/faq/index.cjs +3 -3
  49. package/dist/components/faq/index.js +2 -2
  50. package/dist/components/features/index.cjs +2 -2
  51. package/dist/components/features/index.js +1 -1
  52. package/dist/components/help-center-pages/index.cjs +20 -20
  53. package/dist/components/help-center-pages/index.js +11 -11
  54. package/dist/components/index.cjs +128 -128
  55. package/dist/components/index.js +9 -9
  56. package/dist/components/navigation/index.cjs +2 -2
  57. package/dist/components/navigation/index.js +1 -1
  58. package/dist/components/onboarding-guides/index.cjs +5 -5
  59. package/dist/components/onboarding-guides/index.js +4 -4
  60. package/dist/components/related-content/index.cjs +3 -3
  61. package/dist/components/related-content/index.js +2 -2
  62. package/dist/components/tickets/index.cjs +5 -5
  63. package/dist/components/tickets/index.js +4 -4
  64. package/dist/components/ui/index.cjs +2 -2
  65. package/dist/components/ui/index.js +1 -1
  66. package/dist/index.cjs +10 -2
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.js +9 -1
  69. package/dist/types/video-processing.d.ts +2 -0
  70. package/dist/types/video-processing.d.ts.map +1 -1
  71. package/dist/utils/index.cjs +33 -0
  72. package/dist/utils/index.cjs.map +1 -1
  73. package/dist/utils/index.d.ts +1 -0
  74. package/dist/utils/index.d.ts.map +1 -1
  75. package/dist/utils/index.js +30 -1
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/utils/video-bite-id.cjs +37 -0
  78. package/dist/utils/video-bite-id.cjs.map +1 -0
  79. package/dist/utils/video-bite-id.d.ts +38 -0
  80. package/dist/utils/video-bite-id.d.ts.map +1 -0
  81. package/dist/utils/video-bite-id.js +32 -0
  82. package/dist/utils/video-bite-id.js.map +1 -0
  83. package/package.json +7 -1
  84. package/src/types/video-processing.ts +2 -0
  85. package/src/utils/index.ts +3 -0
  86. package/src/utils/video-bite-id.ts +68 -0
  87. package/dist/chunk-3V5UQ5TU.cjs.map +0 -1
  88. package/dist/chunk-ZH2OEMQJ.js.map +0 -1
  89. /package/dist/{chunk-FSOKLOXF.js.map → chunk-6HROGXG6.js.map} +0 -0
  90. /package/dist/{chunk-2WXQFYNC.js.map → chunk-7NULUAOH.js.map} +0 -0
  91. /package/dist/{chunk-LYK6D7GU.js.map → chunk-CWQQV4YF.js.map} +0 -0
  92. /package/dist/{chunk-3OPSNUPJ.js.map → chunk-F24JBANX.js.map} +0 -0
  93. /package/dist/{chunk-MWIYAVFT.js.map → chunk-GN54QCPI.js.map} +0 -0
  94. /package/dist/{chunk-32BSKGEI.js.map → chunk-M6RZRURG.js.map} +0 -0
  95. /package/dist/{chunk-DVYA3BDU.js.map → chunk-O5TONTLW.js.map} +0 -0
  96. /package/dist/{chunk-E77Z2P3K.js.map → chunk-OY2LB5VP.js.map} +0 -0
  97. /package/dist/{chunk-GQHXJ37C.js.map → chunk-PMANI6KY.js.map} +0 -0
  98. /package/dist/{chunk-BMEA5NPX.js.map → chunk-QAIGNMPP.js.map} +0 -0
  99. /package/dist/{chunk-NFU37EP6.js.map → chunk-U5OADJNL.js.map} +0 -0
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ // src/utils/video-bite-id.ts
4
+ var BITE_ID_RE = /^[0-9a-f]{8}$/;
5
+ function generateBiteId() {
6
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 8);
7
+ }
8
+ function ensureBiteIds(bites) {
9
+ const seen = /* @__PURE__ */ new Set();
10
+ let changed = false;
11
+ const out = bites.map((bite) => {
12
+ const valid = typeof bite.id === "string" && BITE_ID_RE.test(bite.id) && !seen.has(bite.id);
13
+ if (valid) {
14
+ seen.add(bite.id);
15
+ return bite;
16
+ }
17
+ changed = true;
18
+ let id = generateBiteId();
19
+ while (seen.has(id)) id = generateBiteId();
20
+ seen.add(id);
21
+ return { ...bite, id };
22
+ });
23
+ return changed ? { bites: out, changed } : { bites, changed };
24
+ }
25
+ function matchBiteElement(existing, incoming) {
26
+ if (existing.id && incoming.id && existing.id === incoming.id) return true;
27
+ if (incoming.url === existing.url) return true;
28
+ if (existing.source_url && incoming.url === existing.source_url) return true;
29
+ return false;
30
+ }
31
+
32
+ exports.BITE_ID_RE = BITE_ID_RE;
33
+ exports.ensureBiteIds = ensureBiteIds;
34
+ exports.generateBiteId = generateBiteId;
35
+ exports.matchBiteElement = matchBiteElement;
36
+ //# sourceMappingURL=video-bite-id.cjs.map
37
+ //# sourceMappingURL=video-bite-id.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/video-bite-id.ts"],"names":[],"mappings":";;;AAiBO,IAAM,UAAA,GAAa;AAGnB,SAAS,cAAA,GAAyB;AACvC,EAAA,OAAO,MAAA,CAAO,YAAW,CAAE,OAAA,CAAQ,MAAM,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACzD;AAOO,SAAS,cAAyC,KAAA,EAAuD;AAC9G,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,MAAM,GAAA,GAAM,KAAA,CAAM,GAAA,CAAI,CAAA,IAAA,KAAQ;AAC5B,IAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,CAAK,EAAA,KAAO,YAAY,UAAA,CAAW,IAAA,CAAK,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,IAAA,CAAK,GAAA,CAAI,KAAK,EAAE,CAAA;AAC1F,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAA,CAAK,GAAA,CAAI,KAAK,EAAY,CAAA;AAC1B,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,IAAI,KAAK,cAAA,EAAe;AACxB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA,OAAQ,cAAA,EAAe;AACzC,IAAA,IAAA,CAAK,IAAI,EAAE,CAAA;AACX,IAAA,OAAO,EAAE,GAAG,IAAA,EAAM,EAAA,EAAG;AAAA,EACvB,CAAC,CAAA;AACD,EAAA,OAAO,OAAA,GAAU,EAAE,KAAA,EAAO,GAAA,EAAK,SAAQ,GAAI,EAAE,OAAqB,OAAA,EAAQ;AAC5E;AAiBO,SAAS,gBAAA,CAAiB,UAAwB,QAAA,EAAiC;AACxF,EAAA,IAAI,QAAA,CAAS,MAAM,QAAA,CAAS,EAAA,IAAM,SAAS,EAAA,KAAO,QAAA,CAAS,IAAI,OAAO,IAAA;AACtE,EAAA,IAAI,QAAA,CAAS,GAAA,KAAQ,QAAA,CAAS,GAAA,EAAK,OAAO,IAAA;AAC1C,EAAA,IAAI,SAAS,UAAA,IAAc,QAAA,CAAS,GAAA,KAAQ,QAAA,CAAS,YAAY,OAAO,IAAA;AACxE,EAAA,OAAO,KAAA;AACT","file":"video-bite-id.cjs","sourcesContent":["// Video-bite element identity — SSOT for the stable per-element `id` on\n// `video_bites` / `teasers` jsonb elements (see types/video-processing.ts\n// `VideoTeaser.id`) and for the ONE element-matching predicate shared by the\n// hub's bite writers (`saveEntityVideoBitesResult` merge + the\n// `reconcileIncomingBites` clobber guard).\n//\n// Isomorphic by design: runs in the admin editor (browser) and in the hub's\n// Mux reconciliation sweep (Node). Zero dependencies.\n\n/** Shape every matcher/stamper here operates on — the identity-relevant subset of VideoTeaser. */\nexport interface BiteIdentity {\n id?: string\n url: string\n source_url?: string\n}\n\n/** 8 lowercase hex chars. Collision-safe within one array (n≤~100 → p≈1e-6); identity is always scoped to (table, row, column). */\nexport const BITE_ID_RE = /^[0-9a-f]{8}$/\n\n/** Generate a new 8-hex bite element id. */\nexport function generateBiteId(): string {\n return crypto.randomUUID().replace(/-/g, '').slice(0, 8)\n}\n\n/**\n * Stamp missing ids and re-stamp in-array duplicates (copy/pasted bites).\n * Returns the SAME array reference when nothing changed so callers can cheaply\n * skip persistence (`changed === false` ⇒ no write needed).\n */\nexport function ensureBiteIds<T extends { id?: string }>(bites: readonly T[]): { bites: T[]; changed: boolean } {\n const seen = new Set<string>()\n let changed = false\n const out = bites.map(bite => {\n const valid = typeof bite.id === 'string' && BITE_ID_RE.test(bite.id) && !seen.has(bite.id)\n if (valid) {\n seen.add(bite.id as string)\n return bite\n }\n changed = true\n let id = generateBiteId()\n while (seen.has(id)) id = generateBiteId()\n seen.add(id)\n return { ...bite, id }\n })\n return changed ? { bites: out, changed } : { bites: bites as T[], changed }\n}\n\n/**\n * The ONE element-identity predicate. Matching legs (any-of, NOT\n * short-circuit-on-id-inequality — two sides can carry DIFFERENT ids for\n * the same clip, e.g. a redelivered Vizard ingest regenerates ids):\n * 1. `id` equality — when both sides carry one and they match.\n * 2. `incoming.url === existing.url` — plain url (pre-flip stale sessions,\n * plain re-saves).\n * 3. `incoming.url === existing.source_url` — the incoming side still holds\n * the original storage URL of an element the Mux pipeline already flipped\n * to HLS (redelivered ingests, stale editor arrays).\n *\n * Callers that match a whole incoming array against an existing array MUST\n * consume each existing element at most once (multiset matching) — duplicate\n * incoming urls must not all bind to one element and clone its id.\n */\nexport function matchBiteElement(existing: BiteIdentity, incoming: BiteIdentity): boolean {\n if (existing.id && incoming.id && existing.id === incoming.id) return true\n if (incoming.url === existing.url) return true\n if (existing.source_url && incoming.url === existing.source_url) return true\n return false\n}\n"]}
@@ -0,0 +1,38 @@
1
+ /** Shape every matcher/stamper here operates on — the identity-relevant subset of VideoTeaser. */
2
+ export interface BiteIdentity {
3
+ id?: string;
4
+ url: string;
5
+ source_url?: string;
6
+ }
7
+ /** 8 lowercase hex chars. Collision-safe within one array (n≤~100 → p≈1e-6); identity is always scoped to (table, row, column). */
8
+ export declare const BITE_ID_RE: RegExp;
9
+ /** Generate a new 8-hex bite element id. */
10
+ export declare function generateBiteId(): string;
11
+ /**
12
+ * Stamp missing ids and re-stamp in-array duplicates (copy/pasted bites).
13
+ * Returns the SAME array reference when nothing changed so callers can cheaply
14
+ * skip persistence (`changed === false` ⇒ no write needed).
15
+ */
16
+ export declare function ensureBiteIds<T extends {
17
+ id?: string;
18
+ }>(bites: readonly T[]): {
19
+ bites: T[];
20
+ changed: boolean;
21
+ };
22
+ /**
23
+ * The ONE element-identity predicate. Matching legs (any-of, NOT
24
+ * short-circuit-on-id-inequality — two sides can carry DIFFERENT ids for
25
+ * the same clip, e.g. a redelivered Vizard ingest regenerates ids):
26
+ * 1. `id` equality — when both sides carry one and they match.
27
+ * 2. `incoming.url === existing.url` — plain url (pre-flip stale sessions,
28
+ * plain re-saves).
29
+ * 3. `incoming.url === existing.source_url` — the incoming side still holds
30
+ * the original storage URL of an element the Mux pipeline already flipped
31
+ * to HLS (redelivered ingests, stale editor arrays).
32
+ *
33
+ * Callers that match a whole incoming array against an existing array MUST
34
+ * consume each existing element at most once (multiset matching) — duplicate
35
+ * incoming urls must not all bind to one element and clone its id.
36
+ */
37
+ export declare function matchBiteElement(existing: BiteIdentity, incoming: BiteIdentity): boolean;
38
+ //# sourceMappingURL=video-bite-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-bite-id.d.ts","sourceRoot":"","sources":["../../src/utils/video-bite-id.ts"],"names":[],"mappings":"AASA,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,mIAAmI;AACnI,eAAO,MAAM,UAAU,QAAkB,CAAA;AAEzC,4CAA4C;AAC5C,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAgB9G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAKxF"}
@@ -0,0 +1,32 @@
1
+ // src/utils/video-bite-id.ts
2
+ var BITE_ID_RE = /^[0-9a-f]{8}$/;
3
+ function generateBiteId() {
4
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 8);
5
+ }
6
+ function ensureBiteIds(bites) {
7
+ const seen = /* @__PURE__ */ new Set();
8
+ let changed = false;
9
+ const out = bites.map((bite) => {
10
+ const valid = typeof bite.id === "string" && BITE_ID_RE.test(bite.id) && !seen.has(bite.id);
11
+ if (valid) {
12
+ seen.add(bite.id);
13
+ return bite;
14
+ }
15
+ changed = true;
16
+ let id = generateBiteId();
17
+ while (seen.has(id)) id = generateBiteId();
18
+ seen.add(id);
19
+ return { ...bite, id };
20
+ });
21
+ return changed ? { bites: out, changed } : { bites, changed };
22
+ }
23
+ function matchBiteElement(existing, incoming) {
24
+ if (existing.id && incoming.id && existing.id === incoming.id) return true;
25
+ if (incoming.url === existing.url) return true;
26
+ if (existing.source_url && incoming.url === existing.source_url) return true;
27
+ return false;
28
+ }
29
+
30
+ export { BITE_ID_RE, ensureBiteIds, generateBiteId, matchBiteElement };
31
+ //# sourceMappingURL=video-bite-id.js.map
32
+ //# sourceMappingURL=video-bite-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/video-bite-id.ts"],"names":[],"mappings":";AAiBO,IAAM,UAAA,GAAa;AAGnB,SAAS,cAAA,GAAyB;AACvC,EAAA,OAAO,MAAA,CAAO,YAAW,CAAE,OAAA,CAAQ,MAAM,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACzD;AAOO,SAAS,cAAyC,KAAA,EAAuD;AAC9G,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,MAAM,GAAA,GAAM,KAAA,CAAM,GAAA,CAAI,CAAA,IAAA,KAAQ;AAC5B,IAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,CAAK,EAAA,KAAO,YAAY,UAAA,CAAW,IAAA,CAAK,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,IAAA,CAAK,GAAA,CAAI,KAAK,EAAE,CAAA;AAC1F,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAA,CAAK,GAAA,CAAI,KAAK,EAAY,CAAA;AAC1B,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,IAAI,KAAK,cAAA,EAAe;AACxB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA,OAAQ,cAAA,EAAe;AACzC,IAAA,IAAA,CAAK,IAAI,EAAE,CAAA;AACX,IAAA,OAAO,EAAE,GAAG,IAAA,EAAM,EAAA,EAAG;AAAA,EACvB,CAAC,CAAA;AACD,EAAA,OAAO,OAAA,GAAU,EAAE,KAAA,EAAO,GAAA,EAAK,SAAQ,GAAI,EAAE,OAAqB,OAAA,EAAQ;AAC5E;AAiBO,SAAS,gBAAA,CAAiB,UAAwB,QAAA,EAAiC;AACxF,EAAA,IAAI,QAAA,CAAS,MAAM,QAAA,CAAS,EAAA,IAAM,SAAS,EAAA,KAAO,QAAA,CAAS,IAAI,OAAO,IAAA;AACtE,EAAA,IAAI,QAAA,CAAS,GAAA,KAAQ,QAAA,CAAS,GAAA,EAAK,OAAO,IAAA;AAC1C,EAAA,IAAI,SAAS,UAAA,IAAc,QAAA,CAAS,GAAA,KAAQ,QAAA,CAAS,YAAY,OAAO,IAAA;AACxE,EAAA,OAAO,KAAA;AACT","file":"video-bite-id.js","sourcesContent":["// Video-bite element identity — SSOT for the stable per-element `id` on\n// `video_bites` / `teasers` jsonb elements (see types/video-processing.ts\n// `VideoTeaser.id`) and for the ONE element-matching predicate shared by the\n// hub's bite writers (`saveEntityVideoBitesResult` merge + the\n// `reconcileIncomingBites` clobber guard).\n//\n// Isomorphic by design: runs in the admin editor (browser) and in the hub's\n// Mux reconciliation sweep (Node). Zero dependencies.\n\n/** Shape every matcher/stamper here operates on — the identity-relevant subset of VideoTeaser. */\nexport interface BiteIdentity {\n id?: string\n url: string\n source_url?: string\n}\n\n/** 8 lowercase hex chars. Collision-safe within one array (n≤~100 → p≈1e-6); identity is always scoped to (table, row, column). */\nexport const BITE_ID_RE = /^[0-9a-f]{8}$/\n\n/** Generate a new 8-hex bite element id. */\nexport function generateBiteId(): string {\n return crypto.randomUUID().replace(/-/g, '').slice(0, 8)\n}\n\n/**\n * Stamp missing ids and re-stamp in-array duplicates (copy/pasted bites).\n * Returns the SAME array reference when nothing changed so callers can cheaply\n * skip persistence (`changed === false` ⇒ no write needed).\n */\nexport function ensureBiteIds<T extends { id?: string }>(bites: readonly T[]): { bites: T[]; changed: boolean } {\n const seen = new Set<string>()\n let changed = false\n const out = bites.map(bite => {\n const valid = typeof bite.id === 'string' && BITE_ID_RE.test(bite.id) && !seen.has(bite.id)\n if (valid) {\n seen.add(bite.id as string)\n return bite\n }\n changed = true\n let id = generateBiteId()\n while (seen.has(id)) id = generateBiteId()\n seen.add(id)\n return { ...bite, id }\n })\n return changed ? { bites: out, changed } : { bites: bites as T[], changed }\n}\n\n/**\n * The ONE element-identity predicate. Matching legs (any-of, NOT\n * short-circuit-on-id-inequality — two sides can carry DIFFERENT ids for\n * the same clip, e.g. a redelivered Vizard ingest regenerates ids):\n * 1. `id` equality — when both sides carry one and they match.\n * 2. `incoming.url === existing.url` — plain url (pre-flip stale sessions,\n * plain re-saves).\n * 3. `incoming.url === existing.source_url` — the incoming side still holds\n * the original storage URL of an element the Mux pipeline already flipped\n * to HLS (redelivered ingests, stale editor arrays).\n *\n * Callers that match a whole incoming array against an existing array MUST\n * consume each existing element at most once (multiset matching) — duplicate\n * incoming urls must not all bind to one element and clone its id.\n */\nexport function matchBiteElement(existing: BiteIdentity, incoming: BiteIdentity): boolean {\n if (existing.id && incoming.id && existing.id === incoming.id) return true\n if (incoming.url === existing.url) return true\n if (existing.source_url && incoming.url === existing.source_url) return true\n return false\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.436",
3
+ "version": "0.0.437",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -198,6 +198,12 @@
198
198
  "require": "./dist/utils/humanity-signals.cjs",
199
199
  "default": "./dist/utils/humanity-signals.js"
200
200
  },
201
+ "./utils/video-bite-id": {
202
+ "types": "./dist/utils/video-bite-id.d.ts",
203
+ "import": "./dist/utils/video-bite-id.js",
204
+ "require": "./dist/utils/video-bite-id.cjs",
205
+ "default": "./dist/utils/video-bite-id.js"
206
+ },
201
207
  "./styles": {
202
208
  "types": "./src/styles/index.d.ts",
203
209
  "import": "./src/styles/index.css",
@@ -12,7 +12,9 @@
12
12
  * below stays a separate shape by design.
13
13
  */
14
14
  export interface VideoTeaser {
15
+ id?: string // Stable 8-char lowercase-hex element id (see utils/video-bite-id.ts). Stamped at creation (editor + AI save) and lazily backfilled by the Mux reconciliation sweep. Identity key for the Mux transcode pipeline — never derived from url (urls have real-world duplicates).
15
16
  url: string
17
+ source_url?: string // Original storage URL, set by the Mux pipeline when it flips `url` to the HLS playback URL. Merge-identity leg for redelivered ingests + transcode dedup. Never set by editors.
16
18
  title?: string
17
19
  thumbnail_url?: string // Optional thumbnail image URL for video preview. If not provided, video player will show first frame automatically.
18
20
  published?: boolean // Controls visibility on public preview page (default: false, admin must select)
@@ -76,6 +76,9 @@ export { getPlatformIconComponent as getPlatformLogo } from './platform-config'
76
76
  export * from './tool-utils'
77
77
  // Shell type utilities
78
78
  export * from './shell-utils'
79
+ // Video-bite element identity (id stamping + the ONE matching predicate) —
80
+ // isomorphic; used by the admin bites editor AND the hub's Mux sweep/writers.
81
+ export * from './video-bite-id'
79
82
  // OS type utilities
80
83
  export * from './os-utils'
81
84
  // Phone utilities
@@ -0,0 +1,68 @@
1
+ // Video-bite element identity — SSOT for the stable per-element `id` on
2
+ // `video_bites` / `teasers` jsonb elements (see types/video-processing.ts
3
+ // `VideoTeaser.id`) and for the ONE element-matching predicate shared by the
4
+ // hub's bite writers (`saveEntityVideoBitesResult` merge + the
5
+ // `reconcileIncomingBites` clobber guard).
6
+ //
7
+ // Isomorphic by design: runs in the admin editor (browser) and in the hub's
8
+ // Mux reconciliation sweep (Node). Zero dependencies.
9
+
10
+ /** Shape every matcher/stamper here operates on — the identity-relevant subset of VideoTeaser. */
11
+ export interface BiteIdentity {
12
+ id?: string
13
+ url: string
14
+ source_url?: string
15
+ }
16
+
17
+ /** 8 lowercase hex chars. Collision-safe within one array (n≤~100 → p≈1e-6); identity is always scoped to (table, row, column). */
18
+ export const BITE_ID_RE = /^[0-9a-f]{8}$/
19
+
20
+ /** Generate a new 8-hex bite element id. */
21
+ export function generateBiteId(): string {
22
+ return crypto.randomUUID().replace(/-/g, '').slice(0, 8)
23
+ }
24
+
25
+ /**
26
+ * Stamp missing ids and re-stamp in-array duplicates (copy/pasted bites).
27
+ * Returns the SAME array reference when nothing changed so callers can cheaply
28
+ * skip persistence (`changed === false` ⇒ no write needed).
29
+ */
30
+ export function ensureBiteIds<T extends { id?: string }>(bites: readonly T[]): { bites: T[]; changed: boolean } {
31
+ const seen = new Set<string>()
32
+ let changed = false
33
+ const out = bites.map(bite => {
34
+ const valid = typeof bite.id === 'string' && BITE_ID_RE.test(bite.id) && !seen.has(bite.id)
35
+ if (valid) {
36
+ seen.add(bite.id as string)
37
+ return bite
38
+ }
39
+ changed = true
40
+ let id = generateBiteId()
41
+ while (seen.has(id)) id = generateBiteId()
42
+ seen.add(id)
43
+ return { ...bite, id }
44
+ })
45
+ return changed ? { bites: out, changed } : { bites: bites as T[], changed }
46
+ }
47
+
48
+ /**
49
+ * The ONE element-identity predicate. Matching legs (any-of, NOT
50
+ * short-circuit-on-id-inequality — two sides can carry DIFFERENT ids for
51
+ * the same clip, e.g. a redelivered Vizard ingest regenerates ids):
52
+ * 1. `id` equality — when both sides carry one and they match.
53
+ * 2. `incoming.url === existing.url` — plain url (pre-flip stale sessions,
54
+ * plain re-saves).
55
+ * 3. `incoming.url === existing.source_url` — the incoming side still holds
56
+ * the original storage URL of an element the Mux pipeline already flipped
57
+ * to HLS (redelivered ingests, stale editor arrays).
58
+ *
59
+ * Callers that match a whole incoming array against an existing array MUST
60
+ * consume each existing element at most once (multiset matching) — duplicate
61
+ * incoming urls must not all bind to one element and clone its id.
62
+ */
63
+ export function matchBiteElement(existing: BiteIdentity, incoming: BiteIdentity): boolean {
64
+ if (existing.id && incoming.id && existing.id === incoming.id) return true
65
+ if (incoming.url === existing.url) return true
66
+ if (existing.source_url && incoming.url === existing.source_url) return true
67
+ return false
68
+ }