@flamingo-stack/openframe-frontend-core 0.0.436 → 0.0.437-snapshot.20260716153430
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3V5UQ5TU.cjs → chunk-2DCP2TIF.cjs} +37 -4
- package/dist/chunk-2DCP2TIF.cjs.map +1 -0
- package/dist/{chunk-MWIYAVFT.js → chunk-44HNH57W.js} +4 -4
- package/dist/{chunk-BMEA5NPX.js → chunk-5D5SJOLE.js} +4 -4
- package/dist/{chunk-ZH2OEMQJ.js → chunk-6ETVJ5ON.js} +36 -3
- package/dist/chunk-6ETVJ5ON.js.map +1 -0
- package/dist/{chunk-NGBO5Z5O.cjs → chunk-6R5SZPAQ.cjs} +15 -15
- package/dist/{chunk-NGBO5Z5O.cjs.map → chunk-6R5SZPAQ.cjs.map} +1 -1
- package/dist/{chunk-LYK6D7GU.js → chunk-A25RPWYF.js} +2 -2
- package/dist/{chunk-WXNE2BF4.cjs → chunk-B5SGPIJ4.cjs} +37 -37
- package/dist/{chunk-WXNE2BF4.cjs.map → chunk-B5SGPIJ4.cjs.map} +1 -1
- package/dist/{chunk-KVXB3FOB.cjs → chunk-CMP56RUC.cjs} +62 -62
- package/dist/{chunk-KVXB3FOB.cjs.map → chunk-CMP56RUC.cjs.map} +1 -1
- package/dist/{chunk-DVYA3BDU.js → chunk-D5RTZ2WN.js} +4 -4
- package/dist/{chunk-P5EN67AU.cjs → chunk-DTYPYYKD.cjs} +59 -59
- package/dist/{chunk-P5EN67AU.cjs.map → chunk-DTYPYYKD.cjs.map} +1 -1
- package/dist/{chunk-AUSUEKXY.cjs → chunk-H2TQ44VD.cjs} +31 -31
- package/dist/{chunk-AUSUEKXY.cjs.map → chunk-H2TQ44VD.cjs.map} +1 -1
- package/dist/{chunk-NFU37EP6.js → chunk-KBFQF4AV.js} +2 -2
- package/dist/{chunk-3OPSNUPJ.js → chunk-LR6A67KZ.js} +2 -2
- package/dist/{chunk-2QJNX32P.cjs → chunk-PCMDRDYX.cjs} +5 -5
- package/dist/{chunk-2QJNX32P.cjs.map → chunk-PCMDRDYX.cjs.map} +1 -1
- package/dist/{chunk-E77Z2P3K.js → chunk-POFDZH2C.js} +4 -4
- package/dist/{chunk-32BSKGEI.js → chunk-RTHOCS3S.js} +2 -2
- package/dist/{chunk-PXTDEKAY.cjs → chunk-SYTL2VIU.cjs} +7 -7
- package/dist/{chunk-PXTDEKAY.cjs.map → chunk-SYTL2VIU.cjs.map} +1 -1
- package/dist/{chunk-IWYRSGJU.cjs → chunk-VYG2CJFJ.cjs} +9 -9
- package/dist/{chunk-IWYRSGJU.cjs.map → chunk-VYG2CJFJ.cjs.map} +1 -1
- package/dist/{chunk-FSOKLOXF.js → chunk-W2PRXXUD.js} +2 -2
- package/dist/{chunk-32V2VHPE.cjs → chunk-WF7VGEYR.cjs} +26 -26
- package/dist/{chunk-32V2VHPE.cjs.map → chunk-WF7VGEYR.cjs.map} +1 -1
- package/dist/{chunk-E6NRV33G.cjs → chunk-X66KRL5G.cjs} +11 -11
- package/dist/{chunk-E6NRV33G.cjs.map → chunk-X66KRL5G.cjs.map} +1 -1
- package/dist/{chunk-2WXQFYNC.js → chunk-XFSDFO6U.js} +2 -2
- package/dist/{chunk-NRIJNBRG.cjs → chunk-YKP2F2NC.cjs} +26 -26
- package/dist/{chunk-NRIJNBRG.cjs.map → chunk-YKP2F2NC.cjs.map} +1 -1
- package/dist/{chunk-GQHXJ37C.js → chunk-ZEY7A3MP.js} +2 -2
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +128 -128
- package/dist/components/index.js +9 -9
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/index.cjs +10 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -1
- package/dist/types/video-processing.d.ts +2 -0
- package/dist/types/video-processing.d.ts.map +1 -1
- package/dist/utils/index.cjs +33 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +30 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/video-bite-id.cjs +37 -0
- package/dist/utils/video-bite-id.cjs.map +1 -0
- package/dist/utils/video-bite-id.d.ts +38 -0
- package/dist/utils/video-bite-id.d.ts.map +1 -0
- package/dist/utils/video-bite-id.js +32 -0
- package/dist/utils/video-bite-id.js.map +1 -0
- package/package.json +7 -1
- package/src/components/chat/guide-welcome.tsx +3 -3
- package/src/components/ui/tab-navigation.tsx +2 -1
- package/src/styles/app-globals.css +24 -31
- package/src/types/video-processing.ts +2 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/video-bite-id.ts +68 -0
- package/dist/chunk-3V5UQ5TU.cjs.map +0 -1
- package/dist/chunk-ZH2OEMQJ.js.map +0 -1
- /package/dist/{chunk-MWIYAVFT.js.map → chunk-44HNH57W.js.map} +0 -0
- /package/dist/{chunk-BMEA5NPX.js.map → chunk-5D5SJOLE.js.map} +0 -0
- /package/dist/{chunk-LYK6D7GU.js.map → chunk-A25RPWYF.js.map} +0 -0
- /package/dist/{chunk-DVYA3BDU.js.map → chunk-D5RTZ2WN.js.map} +0 -0
- /package/dist/{chunk-NFU37EP6.js.map → chunk-KBFQF4AV.js.map} +0 -0
- /package/dist/{chunk-3OPSNUPJ.js.map → chunk-LR6A67KZ.js.map} +0 -0
- /package/dist/{chunk-E77Z2P3K.js.map → chunk-POFDZH2C.js.map} +0 -0
- /package/dist/{chunk-32BSKGEI.js.map → chunk-RTHOCS3S.js.map} +0 -0
- /package/dist/{chunk-FSOKLOXF.js.map → chunk-W2PRXXUD.js.map} +0 -0
- /package/dist/{chunk-2WXQFYNC.js.map → chunk-XFSDFO6U.js.map} +0 -0
- /package/dist/{chunk-GQHXJ37C.js.map → chunk-ZEY7A3MP.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.
|
|
3
|
+
"version": "0.0.437-snapshot.20260716153430",
|
|
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",
|
|
@@ -196,13 +196,13 @@ export function GuideWelcome({
|
|
|
196
196
|
`ChatQuickActionRow` in `wrap` mode: ALL chips render (no "⋯" overflow
|
|
197
197
|
collapse) so every action is directly hoverable; hover/focus previews
|
|
198
198
|
the action's full prompt in the composer, click sends it. Capped height
|
|
199
|
-
+ internal scroll
|
|
200
|
-
|
|
199
|
+
+ internal scroll (scrollbar-hide: scrollable, bar never shows) so a
|
|
200
|
+
long list can't squeeze the composer on short screens. */}
|
|
201
201
|
{quickActions.length > 0 && (
|
|
202
202
|
<ChatQuickActionRow
|
|
203
203
|
wrap
|
|
204
204
|
chips={chipItems}
|
|
205
|
-
className="max-h-28 overflow-y-auto scrollbar-
|
|
205
|
+
className="max-h-28 overflow-y-auto scrollbar-hide"
|
|
206
206
|
/>
|
|
207
207
|
)}
|
|
208
208
|
</div>
|
|
@@ -214,7 +214,8 @@ export function TabNavigation({
|
|
|
214
214
|
return (
|
|
215
215
|
<>
|
|
216
216
|
<div className={cn("relative w-full", className)}>
|
|
217
|
-
|
|
217
|
+
{/* scrollbar-hide: tabs stay swipe/wheel-scrollable, bar never shows */}
|
|
218
|
+
<div ref={scrollRef} className="flex gap-[var(--spacing-system-xxs)] items-center justify-start h-full overflow-x-auto overflow-y-hidden scrollbar-hide">
|
|
218
219
|
{tabs.map((tab) => {
|
|
219
220
|
const isActive = activeTab === tab.id
|
|
220
221
|
|
|
@@ -3,37 +3,12 @@
|
|
|
3
3
|
@apply border-border;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
/* Elevator scrollbar
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* Elevator scrollbar (ODS): 8px track + draggable rounded thumb */
|
|
16
|
-
*::-webkit-scrollbar {
|
|
17
|
-
width: 8px;
|
|
18
|
-
height: 8px;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
*::-webkit-scrollbar-track {
|
|
22
|
-
background-color: var(--ods-system-greys-soft-grey);
|
|
23
|
-
border-radius: 4px;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
*::-webkit-scrollbar-thumb {
|
|
27
|
-
background-color: var(--ods-system-greys-grey);
|
|
28
|
-
border-radius: 4px;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
*::-webkit-scrollbar-thumb:hover {
|
|
32
|
-
background-color: var(--ods-system-greys-grey-hover);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
*::-webkit-scrollbar-corner {
|
|
36
|
-
background-color: transparent;
|
|
6
|
+
/* Elevator scrollbar (ODS): thin grey draggable thumb on a transparent track.
|
|
7
|
+
Standard properties only — no ::-webkit-scrollbar, so the platform keeps its
|
|
8
|
+
native rendering mode (e.g. auto-hiding overlay scrollbars on macOS). */
|
|
9
|
+
* {
|
|
10
|
+
scrollbar-width: thin;
|
|
11
|
+
scrollbar-color: var(--ods-system-greys-grey) transparent;
|
|
37
12
|
}
|
|
38
13
|
|
|
39
14
|
body {
|
|
@@ -76,6 +51,12 @@ html {
|
|
|
76
51
|
can't re-enable it for an ancestor scroll container. The self-driven rAF
|
|
77
52
|
tween in scroll-into-view.ts is the primary fix; this is belt-and-braces. */
|
|
78
53
|
overflow-anchor: none;
|
|
54
|
+
/* Reserve the scrollbar gutter permanently (classic scrollbars only — on
|
|
55
|
+
macOS overlay scrollbars this is a no-op). When a Drawer/Dialog locks the
|
|
56
|
+
page with `overflow: hidden`, the gutter stays reserved (per spec `stable`
|
|
57
|
+
applies to hidden too), so the content never shifts when the scrollbar
|
|
58
|
+
comes and goes. Pairs with the `data-scroll-locked` override below. */
|
|
59
|
+
scrollbar-gutter: stable;
|
|
79
60
|
}
|
|
80
61
|
|
|
81
62
|
body {
|
|
@@ -84,6 +65,18 @@ body {
|
|
|
84
65
|
overflow-anchor: none;
|
|
85
66
|
}
|
|
86
67
|
|
|
68
|
+
/* Radix Dialog/Drawer scroll-lock (react-remove-scroll-bar) compensates for
|
|
69
|
+
the disappearing scrollbar by injecting
|
|
70
|
+
`body[data-scroll-locked] { margin-right: <gap>px !important }`.
|
|
71
|
+
With `scrollbar-gutter: stable` above, the scrollbar space never collapses,
|
|
72
|
+
so that compensation is no longer needed — it becomes the layout shift
|
|
73
|
+
itself (and it never fixed `position: fixed` chrome like the header anyway).
|
|
74
|
+
The doubled attribute selector out-specifies the injected rule (0,2,1 vs
|
|
75
|
+
0,1,1) so our `!important` wins. */
|
|
76
|
+
body[data-scroll-locked][data-scroll-locked] {
|
|
77
|
+
margin-right: 0 !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
87
80
|
/* Light mode specific styles (used in alternatives-list.tsx) */
|
|
88
81
|
:root:not(.dark) .light-mode-shadow {
|
|
89
82
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
@@ -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)
|
package/src/utils/index.ts
CHANGED
|
@@ -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
|
+
}
|