@flamingo-stack/openframe-frontend-core 0.0.568 → 0.0.569-snapshot.20260827034838
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-2LKVYI7H.js → chunk-3H6NH2AD.js} +6 -6
- package/dist/{chunk-KZ3PIUCH.cjs → chunk-3YO2ZIZL.cjs} +14 -14
- package/dist/{chunk-KZ3PIUCH.cjs.map → chunk-3YO2ZIZL.cjs.map} +1 -1
- package/dist/{chunk-L22CSZZU.cjs → chunk-4BJ4FQPG.cjs} +18 -18
- package/dist/{chunk-L22CSZZU.cjs.map → chunk-4BJ4FQPG.cjs.map} +1 -1
- package/dist/{chunk-EYXZF46V.js → chunk-5NVVVIHJ.js} +1038 -922
- package/dist/chunk-5NVVVIHJ.js.map +1 -0
- package/dist/{chunk-P7QGTNEW.cjs → chunk-6WXL6CIL.cjs} +101 -101
- package/dist/{chunk-P7QGTNEW.cjs.map → chunk-6WXL6CIL.cjs.map} +1 -1
- package/dist/{chunk-CEAAOVCV.cjs → chunk-AJPGQCZ2.cjs} +1 -1
- package/dist/chunk-AJPGQCZ2.cjs.map +1 -0
- package/dist/{chunk-NK3M6F5A.cjs → chunk-AQ6MIFNX.cjs} +148 -13
- package/dist/chunk-AQ6MIFNX.cjs.map +1 -0
- package/dist/{chunk-6PWUUWCO.cjs → chunk-CSCAOET4.cjs} +42 -42
- package/dist/{chunk-6PWUUWCO.cjs.map → chunk-CSCAOET4.cjs.map} +1 -1
- package/dist/{chunk-6I76UK2Z.cjs → chunk-CYY2PEYQ.cjs} +3 -3
- package/dist/{chunk-6I76UK2Z.cjs.map → chunk-CYY2PEYQ.cjs.map} +1 -1
- package/dist/{chunk-Y2BYHTEN.cjs → chunk-D463PBIY.cjs} +23 -23
- package/dist/{chunk-Y2BYHTEN.cjs.map → chunk-D463PBIY.cjs.map} +1 -1
- package/dist/{chunk-NKEOD2AD.cjs → chunk-DP2L7US5.cjs} +3 -3
- package/dist/{chunk-NKEOD2AD.cjs.map → chunk-DP2L7US5.cjs.map} +1 -1
- package/dist/{chunk-KZS2DFC2.cjs → chunk-GDPPFPLD.cjs} +42 -42
- package/dist/{chunk-KZS2DFC2.cjs.map → chunk-GDPPFPLD.cjs.map} +1 -1
- package/dist/{chunk-LNUXUOPN.cjs → chunk-HJCXSI7R.cjs} +4 -4
- package/dist/{chunk-LNUXUOPN.cjs.map → chunk-HJCXSI7R.cjs.map} +1 -1
- package/dist/{chunk-RBYMJF2V.cjs → chunk-I7CFA5DO.cjs} +35 -35
- package/dist/{chunk-RBYMJF2V.cjs.map → chunk-I7CFA5DO.cjs.map} +1 -1
- package/dist/{chunk-LXWQLUBR.cjs → chunk-IJA26YDA.cjs} +9 -7
- package/dist/chunk-IJA26YDA.cjs.map +1 -0
- package/dist/{chunk-O3JCKEVG.js → chunk-KYDVOX5J.js} +2 -2
- package/dist/{chunk-UWY722RH.js → chunk-MR6RRXSD.js} +1 -1
- package/dist/chunk-MR6RRXSD.js.map +1 -0
- package/dist/{chunk-HB3IG2MC.js → chunk-MT7IDGCM.js} +3 -3
- package/dist/{chunk-G2PAUMV6.js → chunk-MTORQ4LY.js} +4 -4
- package/dist/{chunk-HKG7BZON.cjs → chunk-PS2TV7WG.cjs} +14 -14
- package/dist/{chunk-HKG7BZON.cjs.map → chunk-PS2TV7WG.cjs.map} +1 -1
- package/dist/{chunk-QVQLNKFS.js → chunk-PTDRJCPW.js} +2 -2
- package/dist/{chunk-DZ2Z7HRF.js → chunk-QIU5B2ZD.js} +3 -3
- package/dist/{chunk-6RSKK4E7.cjs → chunk-RVCCJB6M.cjs} +1278 -1162
- package/dist/chunk-RVCCJB6M.cjs.map +1 -0
- package/dist/{chunk-T7AE7CKV.cjs → chunk-T4MB5TX6.cjs} +7 -7
- package/dist/{chunk-T7AE7CKV.cjs.map → chunk-T4MB5TX6.cjs.map} +1 -1
- package/dist/{chunk-ZARYIFCI.js → chunk-TQER4FVN.js} +5 -3
- package/dist/{chunk-ZARYIFCI.js.map → chunk-TQER4FVN.js.map} +1 -1
- package/dist/{chunk-BZ7B255K.js → chunk-U6NPZMNT.js} +9 -9
- package/dist/{chunk-MQQM4NCN.js → chunk-VZQKENQU.js} +3 -3
- package/dist/{chunk-VEXTUGUA.js → chunk-XWMEHLDK.js} +7 -7
- package/dist/{chunk-ZNEVGXMU.js → chunk-YFLKONLB.js} +139 -4
- package/dist/chunk-YFLKONLB.js.map +1 -0
- package/dist/{chunk-ZXASQL3E.js → chunk-YYESWAAQ.js} +4 -4
- package/dist/{chunk-6TDWCBQM.js → chunk-Z3MSD6ZE.js} +7 -7
- package/dist/{chunk-DHTT77AG.cjs → chunk-Z7DXVKVT.cjs} +78 -78
- package/dist/{chunk-DHTT77AG.cjs.map → chunk-Z7DXVKVT.cjs.map} +1 -1
- package/dist/{chunk-KL6ZDLDF.js → chunk-ZAYZ7JHF.js} +2 -2
- package/dist/{chunk-ZIZJDXUI.js → chunk-ZTGWMXJK.js} +2 -2
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/index.cjs +4 -17
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +3 -16
- package/dist/components/chat/utils/index.d.ts +0 -2
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/claude-embed.d.ts +15 -8
- package/dist/components/embeds/claude-embed.d.ts.map +1 -1
- package/dist/components/embeds/embed-viewer-frame.d.ts +17 -2
- package/dist/components/embeds/embed-viewer-frame.d.ts.map +1 -1
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/help-center-pages/index.cjs +25 -25
- package/dist/components/help-center-pages/index.js +16 -16
- package/dist/components/index.cjs +142 -154
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +14 -26
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +36 -36
- package/dist/components/meeting-scheduler/index.js +5 -5
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/tickets/index.cjs +8 -8
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/index.cjs +4 -16
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +3 -15
- package/dist/components/ui/markdown/sanitize.d.ts.map +1 -1
- package/dist/components/ui/table/table.d.ts +1 -1
- package/dist/components/ui/table/table.d.ts.map +1 -1
- package/dist/components/ui/table/types.d.ts +9 -0
- package/dist/components/ui/table/types.d.ts.map +1 -1
- package/dist/contexts/endpoints-runtime-context.d.ts +6 -0
- package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -2
- package/dist/contexts/index.js +1 -1
- package/dist/hooks/index.cjs +5 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -2
- package/dist/hooks/use-claude-mirror-src.d.ts +38 -0
- package/dist/hooks/use-claude-mirror-src.d.ts.map +1 -0
- package/dist/index.cjs +10 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +15 -13
- package/dist/utils/embed-proxy-auth-storage.d.ts.map +1 -1
- package/dist/utils/embed-url-converters.d.ts +20 -0
- package/dist/utils/embed-url-converters.d.ts.map +1 -1
- package/dist/utils/fullscreen-switch.d.ts +85 -0
- package/dist/utils/fullscreen-switch.d.ts.map +1 -0
- package/dist/utils/index.cjs +114 -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 +113 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/hooks/use-chat-attachments.ts +3 -3
- package/src/components/chat/hooks/use-chat-history-hydration.ts +2 -2
- package/src/components/chat/hooks/use-chat-identity.ts +5 -5
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +3 -3
- package/src/components/chat/utils/.index.md +1 -2
- package/src/components/chat/utils/chat-conversation-storage.ts +2 -2
- package/src/components/chat/utils/index.ts +3 -10
- package/src/components/embeds/claude-embed.tsx +50 -14
- package/src/components/embeds/embed-viewer-frame.tsx +103 -5
- package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +30 -0
- package/src/components/ui/markdown/rich/__tests__/shortcodes.test.ts +8 -0
- package/src/components/ui/markdown/sanitize.ts +4 -0
- package/src/components/ui/table/table.tsx +7 -2
- package/src/components/ui/table/types.ts +9 -0
- package/src/contexts/endpoints-runtime-context.tsx +6 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-claude-mirror-src.ts +103 -0
- package/src/utils/.embed-proxy-auth-storage.md +2 -2
- package/src/utils/embed-proxy-auth-storage.ts +19 -14
- package/src/utils/embed-url-converters.ts +45 -0
- package/src/utils/fullscreen-switch.ts +180 -0
- package/src/utils/index.ts +8 -6
- package/dist/chunk-6RSKK4E7.cjs.map +0 -1
- package/dist/chunk-CEAAOVCV.cjs.map +0 -1
- package/dist/chunk-EYXZF46V.js.map +0 -1
- package/dist/chunk-LXWQLUBR.cjs.map +0 -1
- package/dist/chunk-NK3M6F5A.cjs.map +0 -1
- package/dist/chunk-UWY722RH.js.map +0 -1
- package/dist/chunk-ZNEVGXMU.js.map +0 -1
- package/dist/components/chat/utils/chat-authed-fetch.d.ts +0 -15
- package/dist/components/chat/utils/chat-authed-fetch.d.ts.map +0 -1
- package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts +0 -16
- package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts.map +0 -1
- package/src/components/chat/utils/.chat-authed-fetch.md +0 -25
- package/src/components/chat/utils/.chat-proxy-auth-storage.md +0 -38
- package/src/components/chat/utils/chat-authed-fetch.ts +0 -17
- package/src/components/chat/utils/chat-proxy-auth-storage.ts +0 -25
- /package/dist/{chunk-2LKVYI7H.js.map → chunk-3H6NH2AD.js.map} +0 -0
- /package/dist/{chunk-O3JCKEVG.js.map → chunk-KYDVOX5J.js.map} +0 -0
- /package/dist/{chunk-HB3IG2MC.js.map → chunk-MT7IDGCM.js.map} +0 -0
- /package/dist/{chunk-G2PAUMV6.js.map → chunk-MTORQ4LY.js.map} +0 -0
- /package/dist/{chunk-QVQLNKFS.js.map → chunk-PTDRJCPW.js.map} +0 -0
- /package/dist/{chunk-DZ2Z7HRF.js.map → chunk-QIU5B2ZD.js.map} +0 -0
- /package/dist/{chunk-BZ7B255K.js.map → chunk-U6NPZMNT.js.map} +0 -0
- /package/dist/{chunk-MQQM4NCN.js.map → chunk-VZQKENQU.js.map} +0 -0
- /package/dist/{chunk-VEXTUGUA.js.map → chunk-XWMEHLDK.js.map} +0 -0
- /package/dist/{chunk-ZXASQL3E.js.map → chunk-YYESWAAQ.js.map} +0 -0
- /package/dist/{chunk-6TDWCBQM.js.map → chunk-Z3MSD6ZE.js.map} +0 -0
- /package/dist/{chunk-KL6ZDLDF.js.map → chunk-ZAYZ7JHF.js.map} +0 -0
- /package/dist/{chunk-ZIZJDXUI.js.map → chunk-ZTGWMXJK.js.map} +0 -0
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `AbortController` for clean remove-mid-upload.
|
|
10
10
|
*
|
|
11
11
|
* Auth propagation:
|
|
12
|
-
* - `
|
|
12
|
+
* - `embedAuthedFetch` carries bearer-act-as headers + Supabase
|
|
13
13
|
* session cookies — used both for minting the upload URL and as
|
|
14
14
|
* the auth path before falling through to the signed PUT.
|
|
15
15
|
* - The byte-PUT to the signed URL uses an inline `uploadWithProgress`
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
27
27
|
import { fileTypeFromBlob } from 'file-type'
|
|
28
28
|
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
29
|
-
import {
|
|
29
|
+
import { embedAuthedFetch } from '../../../utils/embed-authed-fetch'
|
|
30
30
|
import {
|
|
31
31
|
CHAT_ATTACHMENT_MIME_TYPES,
|
|
32
32
|
CHAT_ATTACHMENT_CONCURRENT_UPLOADS_PER_USER,
|
|
@@ -243,7 +243,7 @@ export function useChatAttachments(): UseChatAttachmentsApi {
|
|
|
243
243
|
// bearer-act-as headers (if present).
|
|
244
244
|
updateOne(att.id, { status: 'uploading', progress: 5 })
|
|
245
245
|
|
|
246
|
-
const urlResp = await
|
|
246
|
+
const urlResp = await embedAuthedFetch(uploadUrlEndpoint, {
|
|
247
247
|
method: 'POST',
|
|
248
248
|
headers: { 'Content-Type': 'application/json' },
|
|
249
249
|
signal: ctrl?.signal,
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { useEffect, useRef, useState, type MutableRefObject } from 'react'
|
|
21
21
|
import type { Message } from './use-chat'
|
|
22
|
-
import {
|
|
22
|
+
import { embedAuthedFetch } from '../../../utils/embed-authed-fetch'
|
|
23
23
|
import { AUTO_CONTINUATION_DIRECTIVE_PREFIX } from '../utils/auto-continuation-directive'
|
|
24
24
|
|
|
25
25
|
export interface UseChatHistoryHydrationArgs {
|
|
@@ -79,7 +79,7 @@ export function useChatHistoryHydration({
|
|
|
79
79
|
setIsHydratingHistory(true)
|
|
80
80
|
;(async () => {
|
|
81
81
|
try {
|
|
82
|
-
const res = await
|
|
82
|
+
const res = await embedAuthedFetch(
|
|
83
83
|
`${historyUrl}?conversationId=${encodeURIComponent(conversationId)}`,
|
|
84
84
|
{ method: 'GET' },
|
|
85
85
|
)
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
import { createContext, createElement, useContext, useEffect, useState, type ReactNode } from 'react'
|
|
45
45
|
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context'
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
46
|
+
import { embedAuthedFetch } from '../../../utils/embed-authed-fetch'
|
|
47
|
+
import { getEmbedProxyAuth } from '../../../utils/embed-proxy-auth-storage'
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Wire-shape for the identity route response. Mirrors the hub's
|
|
@@ -109,10 +109,10 @@ const ANON_DEFAULTS: ChatIdentityResponse = {
|
|
|
109
109
|
function useResolveChatIdentity(enabled: boolean): ChatIdentitySurface {
|
|
110
110
|
const runtime = useRequiredChatRuntime()
|
|
111
111
|
const url = runtime.endpoints.identityUrl
|
|
112
|
-
// `
|
|
112
|
+
// `getEmbedProxyAuth()` reads localStorage every render. If the user
|
|
113
113
|
// pastes bearer creds mid-session (via the `/debug` creds bar),
|
|
114
114
|
// their email arrives here and the effect's dep changes → refetch.
|
|
115
|
-
const proxyEmail =
|
|
115
|
+
const proxyEmail = getEmbedProxyAuth()?.email ?? null
|
|
116
116
|
|
|
117
117
|
const [data, setData] = useState<ChatIdentityResponse>(ANON_DEFAULTS)
|
|
118
118
|
// Only "loading" if THIS instance will actually fetch. A disabled
|
|
@@ -126,7 +126,7 @@ function useResolveChatIdentity(enabled: boolean): ChatIdentitySurface {
|
|
|
126
126
|
const ctrl = new AbortController()
|
|
127
127
|
|
|
128
128
|
setIsLoading(true)
|
|
129
|
-
|
|
129
|
+
embedAuthedFetch(url, { signal: ctrl.signal })
|
|
130
130
|
.then(async (resp) => {
|
|
131
131
|
if (!resp.ok) return ANON_DEFAULTS
|
|
132
132
|
return (await resp.json()) as ChatIdentityResponse
|
|
@@ -57,7 +57,7 @@ import type { ChatRef } from '../chat-ref.types'
|
|
|
57
57
|
import type { Message } from '../types/message.types'
|
|
58
58
|
import type { MessageSegment } from '../types/message.types'
|
|
59
59
|
import { useSlashCommandRegistry, type SlashCommandSummary } from './use-slash-commands'
|
|
60
|
-
import {
|
|
60
|
+
import { embedAuthedFetch } from '../../../utils/embed-authed-fetch'
|
|
61
61
|
import type { ScrollAnchor } from '../utils/scroll-anchor'
|
|
62
62
|
import { buildConfirmToolBody, readServerErrorMessage } from '../../../chat-protocol/confirm-tool'
|
|
63
63
|
import { createSseFrameDecoder } from '../../../chat-protocol/decode'
|
|
@@ -458,10 +458,10 @@ export function useSseChatAdapter(
|
|
|
458
458
|
abortControllerRef.current = ctrl
|
|
459
459
|
|
|
460
460
|
try {
|
|
461
|
-
// `
|
|
461
|
+
// `embedAuthedFetch` carries the bearer-act-as headers (+ Supabase
|
|
462
462
|
// session cookies) — same wrapper `use-chat-attachments` and
|
|
463
463
|
// `use-chat-identity` use.
|
|
464
|
-
const response = await
|
|
464
|
+
const response = await embedAuthedFetch(targetPath, {
|
|
465
465
|
method: 'POST',
|
|
466
466
|
body: JSON.stringify(requestBody),
|
|
467
467
|
signal: ctrl.signal,
|
|
@@ -8,7 +8,7 @@ Barrel export module that consolidates and re-exports all message processing, ch
|
|
|
8
8
|
| **Message Accumulation** | `MessageSegmentAccumulator`, `createMessageSegmentAccumulator`, `AccumulatorCallbacks` |
|
|
9
9
|
| **Historical Messages** | `processHistoricalMessages`, `extractErrorMessages`, `processHistoricalMessagesWithErrors` |
|
|
10
10
|
| **Chat Navigation** | `handleChatNavClick`, `resolveExternalNavigation`, `decideNewTab`, `computeIsNewTab`, `buildAnchorProps` |
|
|
11
|
-
| **Proxy Auth** | `
|
|
11
|
+
| **Proxy Auth** | moved to `utils/` — `getEmbedProxyAuth`, `setEmbedProxyAuth`, `clearEmbedProxyAuth`, `applyProxyAuth`, `embedAuthedFetch` |
|
|
12
12
|
| **Attachments** | `formatChatAttachmentMarkdownForBubble`, `stripChatAttachmentMarkdown`, `buildChatAttachmentViewUrl` |
|
|
13
13
|
| **UI / Styling** | `chatChipClass`, compact card class constants (`COMPACT_CARD_*`), `getStatusColorScheme` |
|
|
14
14
|
| **Icon Registry** | `ICON_REGISTRY`, `getIconComponent`, `normalizeIconKey`, `getDynamicIcon` |
|
|
@@ -21,7 +21,6 @@ Barrel export module that consolidates and re-exports all message processing, ch
|
|
|
21
21
|
import {
|
|
22
22
|
createMessageSegmentAccumulator,
|
|
23
23
|
handleChatNavClick,
|
|
24
|
-
getChatProxyAuth,
|
|
25
24
|
getIconComponent,
|
|
26
25
|
formatChatAttachmentMarkdownForBubble,
|
|
27
26
|
decideNewTab,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
createLocalStorageAdapter,
|
|
20
20
|
type LocalStorageAdapter,
|
|
21
21
|
} from '../../../utils/local-storage-adapter'
|
|
22
|
-
import {
|
|
22
|
+
import { getEmbedProxyAuth } from '../../../utils/embed-proxy-auth-storage'
|
|
23
23
|
|
|
24
24
|
export interface PersistedChatConversation {
|
|
25
25
|
conversationId: string
|
|
@@ -29,7 +29,7 @@ const STORAGE_KEY = 'conversation'
|
|
|
29
29
|
|
|
30
30
|
const namespaceFor = (source: string): string => {
|
|
31
31
|
const base = `mingo-chat-${source}`
|
|
32
|
-
const auth =
|
|
32
|
+
const auth = getEmbedProxyAuth()
|
|
33
33
|
return auth?.email ? `${base}-u-${encodeURIComponent(auth.email.toLowerCase())}` : base
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -89,16 +89,9 @@ export {
|
|
|
89
89
|
type RuntimeMode,
|
|
90
90
|
} from './decide-new-tab'
|
|
91
91
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
getPersistedProxyEmail,
|
|
96
|
-
setChatProxyAuth,
|
|
97
|
-
clearChatProxyAuth,
|
|
98
|
-
applyProxyAuth,
|
|
99
|
-
type ChatProxyAuth,
|
|
100
|
-
} from './chat-proxy-auth-storage'
|
|
101
|
-
export { chatAuthedFetch } from './chat-authed-fetch'
|
|
92
|
+
// Embed-surface auth moved to `utils/` (embed-proxy-auth-storage /
|
|
93
|
+
// embed-authed-fetch) — import the Embed* names from
|
|
94
|
+
// `@flamingo-stack/openframe-frontend-core/utils`.
|
|
102
95
|
|
|
103
96
|
// Pure content helpers
|
|
104
97
|
export { flattenAssistantContent } from './flatten-assistant-content'
|
|
@@ -4,6 +4,7 @@ import { ExternalLink } from 'lucide-react'
|
|
|
4
4
|
import { Button } from '../ui/button/button'
|
|
5
5
|
import { ClaudeIcon } from '../icons/claude-icon'
|
|
6
6
|
import { EmbedViewerFrame } from './embed-viewer-frame'
|
|
7
|
+
import { useClaudeMirrorSrc } from '../../hooks/use-claude-mirror-src'
|
|
7
8
|
import { toClaudeEmbedUrl } from '../../utils/embed-url-converters'
|
|
8
9
|
|
|
9
10
|
export type ClaudeEmbedKind = 'artifact' | 'design'
|
|
@@ -33,24 +34,44 @@ const KIND_HEADING: Record<ClaudeEmbedKind, string> = {
|
|
|
33
34
|
* `{{claude-artifact:URL}}` / `{{claude-design:URL}}` shortcodes, so it looks
|
|
34
35
|
* identical wherever markdown renders — a spec body, a comment, a links rail.
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* the
|
|
37
|
+
* FRAME SOURCE — fully under the hood, in priority order:
|
|
38
|
+
* 1. The host's SELF-HOSTED MIRROR: the url's artifact id derives the
|
|
39
|
+
* storage-view proxy path (`toClaudeMirrorPath`), probed with a
|
|
40
|
+
* 1-byte ranged fetch. When the host has ingested a copy, the frame
|
|
41
|
+
* shows it — claude.ai frame-locks CODE artifacts entirely, so this
|
|
42
|
+
* is the only way those ever render inline.
|
|
43
|
+
* 2. claude.ai's own `/embed` route for a PUBLISHED artifact
|
|
44
|
+
* (`toClaudeEmbedUrl`).
|
|
45
|
+
* 3. Neither → the shared frame's empty state.
|
|
46
|
+
* The consumer passes only the claude URL; "Open in Claude" always
|
|
47
|
+
* targets it, so commenting stays on claude.ai.
|
|
40
48
|
*
|
|
41
|
-
* What defeats the
|
|
42
|
-
* and its `/embed` path answers `frame-ancestors 'self'`),
|
|
43
|
-
* (no embed route), an artifact whose author has not
|
|
44
|
-
* or an http host (mixed content
|
|
49
|
+
* What defeats the claude.ai leg: a Claude CODE url (no Embed settings
|
|
50
|
+
* exist for it, and its `/embed` path answers `frame-ancestors 'self'`),
|
|
51
|
+
* a Claude Design url (no embed route), an artifact whose author has not
|
|
52
|
+
* allow-listed this host, or an http host (mixed content).
|
|
45
53
|
*/
|
|
46
54
|
export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = 'lazy' }: ClaudeEmbedProps) {
|
|
47
|
-
|
|
55
|
+
// Mirror detection lives in `useClaudeMirrorSrc` (derive from
|
|
56
|
+
// EndpointsRuntime + 1-byte ranged probe + background revalidation) — this
|
|
57
|
+
// component stays presentational.
|
|
58
|
+
const { src: mirrorSrc, status } = useClaudeMirrorSrc(url)
|
|
59
|
+
// While the probe is in flight (on a cold view that IS the ~1-2s
|
|
60
|
+
// self-heal), show the shell's loading state rather than the claude.ai
|
|
61
|
+
// fallback — a code artifact's fallback is the empty "no embeddable view"
|
|
62
|
+
// box, and flashing it before the mirror resolves reads as broken.
|
|
63
|
+
const isProbing = status === 'probing'
|
|
64
|
+
// Found → the mirror; otherwise the claude.ai embed (or its empty state).
|
|
65
|
+
// Suppressed while probing so nothing renders under the spinner.
|
|
66
|
+
const embedUrl = isProbing ? null : (mirrorSrc ?? toClaudeEmbedUrl(url))
|
|
48
67
|
return (
|
|
49
68
|
<EmbedViewerFrame
|
|
50
69
|
className="my-6 space-y-3"
|
|
51
70
|
icon={<ClaudeIcon className="w-5 h-5 shrink-0" />}
|
|
52
71
|
title={title?.trim() || KIND_HEADING[kind]}
|
|
53
72
|
titleVariant="h6"
|
|
73
|
+
isLoading={isProbing}
|
|
74
|
+
loadingMessage="Loading artifact…"
|
|
54
75
|
actions={
|
|
55
76
|
<Button
|
|
56
77
|
variant="outline"
|
|
@@ -65,6 +86,10 @@ export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = '
|
|
|
65
86
|
</Button>
|
|
66
87
|
}
|
|
67
88
|
src={embedUrl}
|
|
89
|
+
// The framed artifact has no internal fullscreen button of its own
|
|
90
|
+
// (unlike Figma's player) — the shell provides the toggle for both
|
|
91
|
+
// the claude.ai `/embed` route and a mirror.
|
|
92
|
+
fullscreenControl
|
|
68
93
|
loading={loading}
|
|
69
94
|
height={height}
|
|
70
95
|
// Same frame contract Figma gets — an artifact is a real app: it copies
|
|
@@ -72,11 +97,22 @@ export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = '
|
|
|
72
97
|
allow="clipboard-write; clipboard-read; fullscreen"
|
|
73
98
|
// …but UNLIKE Figma, an artifact is USER-AUTHORED HTML and JS, so it is
|
|
74
99
|
// sandboxed. Omitting `allow-top-navigation` is the point: an artifact
|
|
75
|
-
// cannot navigate the page it is embedded in. `allow-same-origin`
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
100
|
+
// cannot navigate the page it is embedded in. The `allow-same-origin`
|
|
101
|
+
// token is CONDITIONAL on where the frame points:
|
|
102
|
+
// - claude.ai `/embed` (no mirror): Anthropic's own embed snippet
|
|
103
|
+
// uses it, and on a CROSS-ORIGIN frame it grants the frame
|
|
104
|
+
// claude.ai's origin — never ours. The artifact runtime needs
|
|
105
|
+
// it (storage, postMessage handshake).
|
|
106
|
+
// - the derived MIRROR is served from the HOST's OWN origin —
|
|
107
|
+
// `allow-same-origin` + `allow-scripts` there would be a no-op
|
|
108
|
+
// sandbox handing the artifact first-party cookies and
|
|
109
|
+
// `window.parent`. A static mirror renders fine from an opaque
|
|
110
|
+
// origin, so the token is dropped.
|
|
111
|
+
sandbox={
|
|
112
|
+
mirrorSrc
|
|
113
|
+
? 'allow-scripts allow-popups allow-forms'
|
|
114
|
+
: 'allow-scripts allow-same-origin allow-popups allow-forms'
|
|
115
|
+
}
|
|
80
116
|
allowFullScreen
|
|
81
117
|
emptyIcon={<ClaudeIcon className="w-16 h-16 mb-4" />}
|
|
82
118
|
emptyMessage="Open this one in Claude · it has no embeddable view"
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React from 'react'
|
|
3
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { Loader2, Maximize2, Minimize2 } from 'lucide-react'
|
|
4
5
|
import { cn } from '../../utils/cn'
|
|
6
|
+
import { FullscreenSwitchController } from '../../utils/fullscreen-switch'
|
|
7
|
+
import { Button } from '../ui/button/button'
|
|
5
8
|
import { EmbedIframe } from './embed-iframe'
|
|
6
9
|
|
|
7
10
|
/**
|
|
@@ -34,11 +37,19 @@ export interface EmbedViewerFrameProps {
|
|
|
34
37
|
titleVariant?: 'h3' | 'h6'
|
|
35
38
|
/** Rendered verbatim in the header row — the caller owns its own container/state. */
|
|
36
39
|
actions?: React.ReactNode
|
|
37
|
-
/** Iframe source; falsy renders the inline empty state
|
|
40
|
+
/** Iframe source; falsy renders the inline empty state (or the loading
|
|
41
|
+
* state when `isLoading`) under the header. */
|
|
38
42
|
src: string | null | undefined
|
|
43
|
+
/** While true AND there is no `src` yet, the body shows a loading skeleton
|
|
44
|
+
* instead of the empty state — for a viewer that is still resolving where
|
|
45
|
+
* its frame points (e.g. ClaudeEmbed probing for a self-hosted mirror),
|
|
46
|
+
* so a cold first view reads as "loading", not "nothing here". */
|
|
47
|
+
isLoading?: boolean
|
|
39
48
|
/** Large icon for the inline empty state (the viewers pass `w-16 h-16`). */
|
|
40
49
|
emptyIcon?: React.ReactNode
|
|
41
50
|
emptyMessage?: string
|
|
51
|
+
/** Copy under the spinner in the loading state. */
|
|
52
|
+
loadingMessage?: string
|
|
42
53
|
height?: string
|
|
43
54
|
allow?: string
|
|
44
55
|
allowFullScreen?: boolean
|
|
@@ -47,6 +58,13 @@ export interface EmbedViewerFrameProps {
|
|
|
47
58
|
sandbox?: string
|
|
48
59
|
/** Wrapper classes; defaults to the sheets/pdf `space-y-4`. */
|
|
49
60
|
className?: string
|
|
61
|
+
/** Opt-in fullscreen toggle in the header row (rendered after `actions`,
|
|
62
|
+
* only when there is a `src`). Fullscreens the WHOLE frame — header
|
|
63
|
+
* included — so the exit control stays reachable (Esc works too).
|
|
64
|
+
* Exists because some embedded contents ship their own fullscreen
|
|
65
|
+
* button (the Figma player) and some cannot (a self-hosted Claude
|
|
66
|
+
* artifact mirror) — the shell provides the affordance uniformly. */
|
|
67
|
+
fullscreenControl?: boolean
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
export function EmbedViewerFrame({
|
|
@@ -55,17 +73,55 @@ export function EmbedViewerFrame({
|
|
|
55
73
|
titleVariant = 'h3',
|
|
56
74
|
actions,
|
|
57
75
|
src,
|
|
76
|
+
isLoading,
|
|
58
77
|
emptyIcon,
|
|
59
78
|
emptyMessage = 'Content not available',
|
|
79
|
+
loadingMessage = 'Loading…',
|
|
60
80
|
height,
|
|
61
81
|
allow,
|
|
62
82
|
allowFullScreen,
|
|
63
83
|
loading,
|
|
64
84
|
sandbox,
|
|
65
85
|
className,
|
|
86
|
+
fullscreenControl,
|
|
66
87
|
}: EmbedViewerFrameProps) {
|
|
88
|
+
const frameRef = useRef<HTMLDivElement>(null)
|
|
89
|
+
const [isFullscreen, setIsFullscreen] = useState(false)
|
|
90
|
+
|
|
91
|
+
// ONE fullscreen implementation across the library and its hosts:
|
|
92
|
+
// `FullscreenSwitchController` (shared with the hub's deck) owns the
|
|
93
|
+
// Fullscreen API calls, webkit fallbacks, rejection safety, and the
|
|
94
|
+
// change-event-driven state — Esc, F11, buttons and browser chrome all
|
|
95
|
+
// converge on its listener, so the icon can never desync. Here it runs
|
|
96
|
+
// element-level (`target` = this frame; no mask classes — the frame
|
|
97
|
+
// styles off its own state).
|
|
98
|
+
const controllerRef = useRef<FullscreenSwitchController | null>(null)
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!fullscreenControl) return
|
|
101
|
+
const controller = new FullscreenSwitchController({
|
|
102
|
+
target: () => frameRef.current,
|
|
103
|
+
onFullscreenChange: setIsFullscreen,
|
|
104
|
+
})
|
|
105
|
+
controllerRef.current = controller
|
|
106
|
+
controller.attach()
|
|
107
|
+
return () => {
|
|
108
|
+
controllerRef.current = null
|
|
109
|
+
controller.detach()
|
|
110
|
+
}
|
|
111
|
+
}, [fullscreenControl])
|
|
112
|
+
const toggleFullscreen = () => controllerRef.current?.toggle()
|
|
113
|
+
|
|
67
114
|
return (
|
|
68
|
-
<div
|
|
115
|
+
<div
|
|
116
|
+
ref={frameRef}
|
|
117
|
+
className={cn(
|
|
118
|
+
'space-y-[var(--spacing-system-mf)]',
|
|
119
|
+
className,
|
|
120
|
+
// Fullscreen paints its own ground (the fullscreened element
|
|
121
|
+
// otherwise sits on the UA's black backdrop) and scrolls itself.
|
|
122
|
+
isFullscreen && 'overflow-auto bg-ods-bg p-[var(--spacing-system-mf)]',
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
69
125
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
70
126
|
<div className="flex items-center gap-2 min-w-0">
|
|
71
127
|
{icon}
|
|
@@ -75,18 +131,60 @@ export function EmbedViewerFrame({
|
|
|
75
131
|
<span className="text-h6 font-semibold text-ods-text-primary truncate">{title}</span>
|
|
76
132
|
)}
|
|
77
133
|
</div>
|
|
78
|
-
{
|
|
134
|
+
{/* Same responsive action-row idiom as the PDF viewer's button pair
|
|
135
|
+
and every viewer's own action: full-width stacked on mobile,
|
|
136
|
+
inline row from `sm:` up, SAME Button idiom (outline /
|
|
137
|
+
small-legacy / w-4 icon / w-full sm:w-auto). Without the
|
|
138
|
+
fullscreen toggle, `actions` renders EXACTLY as before — the
|
|
139
|
+
wrapper exists only when the toggle joins it, so pre-existing
|
|
140
|
+
viewers (figma/pdf/sheets) are byte-identical. */}
|
|
141
|
+
{fullscreenControl && src ? (
|
|
142
|
+
<div className="flex flex-col gap-[var(--spacing-system-xsf)] sm:flex-row sm:items-center">
|
|
143
|
+
{actions}
|
|
144
|
+
<Button
|
|
145
|
+
variant="outline"
|
|
146
|
+
size="small-legacy"
|
|
147
|
+
onClick={toggleFullscreen}
|
|
148
|
+
aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
|
|
149
|
+
leftIcon={isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
|
|
150
|
+
className="w-full sm:w-auto"
|
|
151
|
+
>
|
|
152
|
+
{isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
153
|
+
</Button>
|
|
154
|
+
</div>
|
|
155
|
+
) : (
|
|
156
|
+
actions
|
|
157
|
+
)}
|
|
79
158
|
</div>
|
|
80
159
|
{src ? (
|
|
81
160
|
<EmbedIframe
|
|
82
161
|
src={src}
|
|
83
162
|
title={title}
|
|
84
|
-
|
|
163
|
+
// 96px = the fullscreen wrapper's own chrome above the iframe:
|
|
164
|
+
// header row (~40px incl. its gap) + the wrapper's mf padding
|
|
165
|
+
// (16px × 2) + the mf space-y gap (16px), rounded up so the
|
|
166
|
+
// frame never forces a scrollbar. Recheck if that chrome changes.
|
|
167
|
+
height={isFullscreen ? 'calc(100vh - 96px)' : height}
|
|
85
168
|
allow={allow}
|
|
86
169
|
allowFullScreen={allowFullScreen}
|
|
87
170
|
loading={loading}
|
|
88
171
|
sandbox={sandbox}
|
|
89
172
|
/>
|
|
173
|
+
) : isLoading ? (
|
|
174
|
+
// SAME box as the empty/iframe state (height + border), so a viewer
|
|
175
|
+
// resolving its frame doesn't collapse then jump — it reads as
|
|
176
|
+
// "loading" and settles into the iframe in place.
|
|
177
|
+
<div
|
|
178
|
+
className="flex flex-col items-center justify-center gap-3 rounded-lg border border-ods-border px-4 py-16 text-center"
|
|
179
|
+
style={height ? { height } : undefined}
|
|
180
|
+
>
|
|
181
|
+
{/* The library's canonical section spinner — `Loader2` +
|
|
182
|
+
`animate-spin` is the only spinner idiom in the codebase (17
|
|
183
|
+
lib call sites, 67 in the hub), and `h-8 w-8 … text-ods-accent`
|
|
184
|
+
is its most-used full-size variant. */}
|
|
185
|
+
<Loader2 className="h-8 w-8 animate-spin text-ods-accent" />
|
|
186
|
+
<p className="text-ods-text-secondary">{loadingMessage}</p>
|
|
187
|
+
</div>
|
|
90
188
|
) : (
|
|
91
189
|
// The SAME box the iframe would have filled: same height, same rounded
|
|
92
190
|
// border. A viewer whose content is missing must not collapse the page
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
buildEffectiveTagSet,
|
|
17
17
|
buildSanitizeSchema,
|
|
18
18
|
escapeUnknownHtmlTags,
|
|
19
|
+
rehypeStripUnsafe,
|
|
19
20
|
SAFE_HTML_TAGS,
|
|
20
21
|
SVG_TAGS,
|
|
21
22
|
} from '../sanitize'
|
|
@@ -352,3 +353,32 @@ describe('link-definition shelters blank in every shape (round 21)', () => {
|
|
|
352
353
|
expect(masked).toContain('## after heading')
|
|
353
354
|
})
|
|
354
355
|
})
|
|
356
|
+
|
|
357
|
+
describe('URL_ATTRS covers hast PROPERTY keys, not attribute names', () => {
|
|
358
|
+
it('strips a javascript: data-url via its camelized hast key', () => {
|
|
359
|
+
// property-information camelizes `data-url` to `dataUrl` BEFORE the
|
|
360
|
+
// strip pass runs - the URL_ATTRS entry must match the lowercased
|
|
361
|
+
// PROPERTY key or the scheme check silently never fires.
|
|
362
|
+
const tree = {
|
|
363
|
+
type: 'root',
|
|
364
|
+
children: [
|
|
365
|
+
{
|
|
366
|
+
type: 'element',
|
|
367
|
+
tagName: 'div',
|
|
368
|
+
properties: {
|
|
369
|
+
className: ['claude-embed'],
|
|
370
|
+
dataUrl: 'javascript:alert(1)',
|
|
371
|
+
dataKind: 'artifact',
|
|
372
|
+
},
|
|
373
|
+
children: [],
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
}
|
|
377
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
378
|
+
;(rehypeStripUnsafe() as (t: unknown) => void)(tree as any)
|
|
379
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
380
|
+
const props = (tree.children[0] as any).properties
|
|
381
|
+
expect(props.dataUrl).toBeUndefined()
|
|
382
|
+
expect(props.dataKind).toBe('artifact')
|
|
383
|
+
})
|
|
384
|
+
})
|
|
@@ -91,6 +91,14 @@ describe('processShortcodes design-doc embeds', () => {
|
|
|
91
91
|
expect(design).toContain('data-kind="design"');
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it('threads the optional pipe title into data-title', () => {
|
|
95
|
+
const titled = processShortcodes('{{claude-artifact:https://claude.ai/a|Brief doc}}');
|
|
96
|
+
expect(titled).toContain('data-title="Brief doc"');
|
|
97
|
+
|
|
98
|
+
const untitled = processShortcodes('{{claude-artifact:https://claude.ai/a}}');
|
|
99
|
+
expect(untitled).not.toContain('data-title');
|
|
100
|
+
});
|
|
101
|
+
|
|
94
102
|
it('escapes the url attribute so a crafted link cannot break out of the block', () => {
|
|
95
103
|
const output = processShortcodes('{{claude-artifact:https://claude.ai/a"><script>alert(1)</script>}}');
|
|
96
104
|
expect(output).not.toContain('<script>');
|
|
@@ -395,6 +395,10 @@ const URL_ATTRS = new Set([
|
|
|
395
395
|
'data',
|
|
396
396
|
'action',
|
|
397
397
|
'background',
|
|
398
|
+
// The embed shortcodes' primary url (`data-url` → `dataUrl`) — it
|
|
399
|
+
// becomes an anchor href ("Open in Claude") and a link-preview fetch
|
|
400
|
+
// target, so a `javascript:`/`data:` scheme there is a live sink too.
|
|
401
|
+
'dataurl',
|
|
398
402
|
])
|
|
399
403
|
|
|
400
404
|
/**
|
|
@@ -21,6 +21,7 @@ function injectSyntheticColumns<T>(
|
|
|
21
21
|
rowActions?: RowAction<T>[],
|
|
22
22
|
renderRowActions?: (item: T) => ReactNode,
|
|
23
23
|
rowHref?: (item: T) => string | null | undefined,
|
|
24
|
+
actionsColumnWidth?: string,
|
|
24
25
|
): TableColumn<T>[] {
|
|
25
26
|
const hasActions = Boolean(rowActions?.length) || Boolean(renderRowActions)
|
|
26
27
|
const result = [...columns]
|
|
@@ -29,7 +30,10 @@ function injectSyntheticColumns<T>(
|
|
|
29
30
|
const actionsColumn: TableColumn<T> = {
|
|
30
31
|
key: '__actions__',
|
|
31
32
|
label: '',
|
|
32
|
-
|
|
33
|
+
// A caller-fixed width keeps the header and every row on ONE grid
|
|
34
|
+
// (see `actionsColumnWidth` in types.ts); the default stays
|
|
35
|
+
// content-sized for back-compat.
|
|
36
|
+
width: actionsColumnWidth ?? 'min-w-[100px] w-auto shrink-0 flex-none',
|
|
33
37
|
align: 'right',
|
|
34
38
|
renderCell: (item: T) => (
|
|
35
39
|
<div className="flex gap-2 items-center justify-end pointer-events-auto" data-no-row-click>
|
|
@@ -120,11 +124,12 @@ export function Table<T = any>({
|
|
|
120
124
|
stickyHeader,
|
|
121
125
|
stickyHeaderOffset,
|
|
122
126
|
animateRowReorder,
|
|
127
|
+
actionsColumnWidth,
|
|
123
128
|
}: TableProps<T>) {
|
|
124
129
|
// Opt-in row-reorder animation: framer-motion is loaded lazily (its own chunk)
|
|
125
130
|
// ONLY when `animateRowReorder` is set, so the default table stays motion-free.
|
|
126
131
|
const tableMotion = useTableMotion(Boolean(animateRowReorder))
|
|
127
|
-
const columnsWithActions = injectSyntheticColumns(columns, rowActions, renderRowActions, rowHref)
|
|
132
|
+
const columnsWithActions = injectSyntheticColumns(columns, rowActions, renderRowActions, rowHref, actionsColumnWidth)
|
|
128
133
|
const getRowHref = (item: T): string | undefined => {
|
|
129
134
|
if (onRowClick || !rowHref) return undefined
|
|
130
135
|
return rowHref(item) ?? undefined
|
|
@@ -110,6 +110,15 @@ export interface TableProps<T = any> {
|
|
|
110
110
|
rowActions?: RowAction<T>[]
|
|
111
111
|
// Custom renderer for the actions area (e.g., kebab/dots menu)
|
|
112
112
|
renderRowActions?: (item: T) => ReactNode
|
|
113
|
+
/**
|
|
114
|
+
* Width class(es) for the synthetic actions column. The default is
|
|
115
|
+
* content-sized (`w-auto`), which mis-aligns every OTHER column between
|
|
116
|
+
* the header and the rows whenever the row actions are wider than the
|
|
117
|
+
* header's "Showing N results" counter (each side sizes to its own
|
|
118
|
+
* content). Pass a FIXED width (e.g. `w-60`) when the actions are wide
|
|
119
|
+
* text buttons so the header and rows share one grid.
|
|
120
|
+
*/
|
|
121
|
+
actionsColumnWidth?: string
|
|
113
122
|
|
|
114
123
|
/**
|
|
115
124
|
* When provided, renders a right-pointing chevron link at the end of every row
|
|
@@ -34,6 +34,12 @@ export interface EndpointsRuntime {
|
|
|
34
34
|
}
|
|
35
35
|
/** POST contact-form submission. */
|
|
36
36
|
contactUrl: string
|
|
37
|
+
/** GET base of the host's private-storage view proxy
|
|
38
|
+
* (`<base>/<bucket>/<object>`). `ClaudeEmbed` derives artifact mirror
|
|
39
|
+
* urls under it (`<base>/design-briefs/<uuid>.html`). OPTIONAL: a host
|
|
40
|
+
* without the proxy simply omits it and claude embeds fall back to
|
|
41
|
+
* claude.ai — no probing happens. */
|
|
42
|
+
storageViewBaseUrl?: string
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export const EndpointsRuntimeContext = createContext<EndpointsRuntime | null>(null)
|
package/src/hooks/index.ts
CHANGED
|
@@ -29,6 +29,10 @@ export * from './use-access-code-integration'
|
|
|
29
29
|
// THE og-placeholder URL hook (endpoints-driven; no injected builder)
|
|
30
30
|
export * from './use-og-placeholder-url'
|
|
31
31
|
|
|
32
|
+
// Claude artifact mirror detection (endpoints-driven; ClaudeEmbed's
|
|
33
|
+
// transparent mirror leg)
|
|
34
|
+
export * from './use-claude-mirror-src'
|
|
35
|
+
|
|
32
36
|
// Deep-link "scroll to URL hash" after data loads. Pairs with URL
|
|
33
37
|
// composers that emit `?<filter>=<id>#<prefix>-<id>` — the filter
|
|
34
38
|
// narrows the list, the hash scrolls the matching DOM id.
|