@tldiagram/core-ui 2.0.7 → 2.0.8

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.
@@ -1,5 +1,12 @@
1
1
  import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../types/vscode-messages';
2
+ declare global {
3
+ interface Window {
4
+ __TLD_VSCODE_API__?: {
5
+ postMessage: (msg: unknown) => void;
6
+ };
7
+ }
8
+ }
2
9
  export declare const vscodeBridge: {
3
- postMessage: (_msg: WebviewToExtensionMessage) => void;
4
- onMessage: (_handler: (msg: ExtensionToWebviewMessage) => void) => (() => void);
10
+ postMessage: (msg: WebviewToExtensionMessage) => void;
11
+ onMessage: (handler: (msg: ExtensionToWebviewMessage) => void) => (() => void);
5
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tldiagram/core-ui",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,6 +30,7 @@
30
30
  "build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json",
31
31
  "prebuild:app": "npm run prepare:icons",
32
32
  "build:app": "tsc && vite build --config vite.config.ts",
33
+ "build:vscode": "vite build --config vite.vscode.config.ts",
33
34
  "lint": "eslint .",
34
35
  "lint:fix": "eslint . --fix",
35
36
  "preview": "vite preview",
package/src/App.tsx CHANGED
@@ -20,17 +20,22 @@ function AppLayout() {
20
20
  const header = useHeader()
21
21
  const node = header && typeof header === 'object' && 'node' in header ? (header as { node: React.ReactNode }).node : header
22
22
  const hideMobileBar = header && typeof header === 'object' && 'hideMobileBar' in header ? !!(header as { hideMobileBar?: boolean }).hideMobileBar : false
23
+ const hideTopBar = typeof window !== 'undefined' && !!window.__TLD_VSCODE__
23
24
 
24
25
  return (
25
26
  <Box h="100dvh" display="flex" flexDirection="column" bg="var(--bg-canvas)" overflow="hidden">
26
- <TopMenuBar hideMobileBar={hideMobileBar} rightSlot={<WorkspacePanel />}>
27
- {node}
28
- </TopMenuBar>
29
- <Box
30
- h={{ base: 'var(--topbar-h-mobile-total)', sm: 'var(--topbar-h-total)' }}
31
- mb={{ base: 'var(--topbar-content-gap)', sm: '0px' }}
32
- flexShrink={0}
33
- />
27
+ {!hideTopBar && (
28
+ <>
29
+ <TopMenuBar hideMobileBar={hideMobileBar} rightSlot={<WorkspacePanel />}>
30
+ {node}
31
+ </TopMenuBar>
32
+ <Box
33
+ h={{ base: 'var(--topbar-h-mobile-total)', sm: 'var(--topbar-h-total)' }}
34
+ mb={{ base: 'var(--topbar-content-gap)', sm: '0px' }}
35
+ flexShrink={0}
36
+ />
37
+ </>
38
+ )}
34
39
  <Box flex="1" minH={0} overflow="hidden" position="relative">
35
40
  <Outlet />
36
41
  </Box>
@@ -1,28 +1,20 @@
1
1
  /**
2
2
  * VS Code webview transport.
3
3
  *
4
- * Replaces the session-cookie transport with Bearer token auth.
5
- * The API key and server URL are injected by the extension host
6
- * via window globals before this script runs.
4
+ * Uses the local tld CLI HTTP server URL injected by the extension host.
7
5
  */
8
6
  import { createConnectTransport } from '@connectrpc/connect-web'
9
7
 
10
8
  declare global {
11
9
  interface Window {
12
- __TLD_API_KEY__?: string
13
10
  __TLD_SERVER_URL__?: string
14
11
  __TLD_DIAGRAM_ID__?: number
15
12
  }
16
13
  }
17
14
 
18
- const serverUrl = (window.__TLD_SERVER_URL__ ?? 'https://tldiagram.com').replace(/\/$/, '')
19
- const apiKey = window.__TLD_API_KEY__ ?? ''
15
+ const serverUrl = (window.__TLD_SERVER_URL__ ?? 'http://127.0.0.1:8060').replace(/\/$/, '')
20
16
 
21
17
  export const transport = createConnectTransport({
22
18
  baseUrl: `${serverUrl}/api`,
23
- fetch: (input, init) => {
24
- const headers = new Headers(init?.headers)
25
- if (apiKey) headers.set('Authorization', `Bearer ${apiKey}`)
26
- return fetch(input, { ...init, headers })
27
- },
19
+ fetch: (input, init) => fetch(input, init),
28
20
  })
@@ -122,6 +122,7 @@ function ElementLibrary({
122
122
  setHasMore(newElements.length === limit)
123
123
  } catch (err) {
124
124
  console.error('Failed to fetch elements:', err)
125
+ setHasMore(false)
125
126
  } finally {
126
127
  isFetching.current = false
127
128
  setLoading(false)
@@ -18,7 +18,17 @@ import {
18
18
  HANDLE_SLOT_COUNT,
19
19
  } from '../utils/edgeDistribution'
20
20
 
21
- function VscodeCodePreview({ filePath, isCanvasMoving }: { filePath: string; isCanvasMoving?: boolean }) {
21
+ function VscodeCodePreview({
22
+ filePath,
23
+ fallbackSymbolName,
24
+ fallbackSymbolKind,
25
+ isCanvasMoving,
26
+ }: {
27
+ filePath: string
28
+ fallbackSymbolName?: string
29
+ fallbackSymbolKind?: string | null
30
+ isCanvasMoving?: boolean
31
+ }) {
22
32
  const [content, setContent] = useState<string | null>(null)
23
33
  const [startLineOffset, setStartLineOffset] = useState(0)
24
34
  const [isOpen, setIsOpen] = useState(false)
@@ -33,8 +43,8 @@ function VscodeCodePreview({ filePath, isCanvasMoving }: { filePath: string; isC
33
43
  }
34
44
  }, [anchorStr])
35
45
  const startLine = typeof anchor?.startLine === 'number' ? anchor.startLine : undefined
36
- const symbolName = typeof anchor?.name === 'string' ? anchor.name : undefined
37
- const symbolKind = typeof anchor?.type === 'string' ? anchor.type : undefined
46
+ const symbolName = typeof anchor?.name === 'string' ? anchor.name : fallbackSymbolName
47
+ const symbolKind = typeof anchor?.type === 'string' ? anchor.type : fallbackSymbolKind ?? undefined
38
48
 
39
49
  useEffect(() => {
40
50
  if (!isOpen) return
@@ -806,6 +816,8 @@ function ElementNode({ data, selected }: Props) {
806
816
  )}
807
817
  <VscodeCodePreview
808
818
  filePath={data.file_path}
819
+ fallbackSymbolName={data.name}
820
+ fallbackSymbolKind={data.kind}
809
821
  isCanvasMoving={data.isCanvasMoving}
810
822
  />
811
823
  </HStack>
@@ -16,7 +16,7 @@ function trimTrailingSlash(value: string): string {
16
16
  return value.replace(/\/+$/, '')
17
17
  }
18
18
 
19
- const serverUrl = trimTrailingSlash(window.__TLD_SERVER_URL__ ?? 'https://tldiagram.com')
19
+ const serverUrl = trimTrailingSlash(window.__TLD_SERVER_URL__ ?? 'http://127.0.0.1:8060')
20
20
 
21
21
  export const appBase = '/app/'
22
22
  export const routerBasename = undefined
@@ -29,9 +29,7 @@ export function apiUrl(path: string): string {
29
29
  }
30
30
 
31
31
  export function fetchApiAsset(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
32
- const headers = new Headers(init?.headers)
33
- if (window.__TLD_API_KEY__) headers.set('Authorization', `Bearer ${window.__TLD_API_KEY__}`)
34
- return fetch(input, { ...init, headers })
32
+ return fetch(input, init)
35
33
  }
36
34
 
37
35
  export function oauthGoogleStartUrl(): string {
@@ -1,7 +1,26 @@
1
1
  import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../types/vscode-messages'
2
2
 
3
- // No-op stub used in web/native builds. Swapped for vscodeBridge-vscode.ts in VS Code builds.
3
+ declare global {
4
+ interface Window {
5
+ __TLD_VSCODE_API__?: {
6
+ postMessage: (msg: unknown) => void
7
+ }
8
+ }
9
+ }
10
+
11
+ // Runtime-aware bridge. It is a no-op in web/native builds, but works when the
12
+ // core UI is bundled directly into the VS Code webview.
4
13
  export const vscodeBridge = {
5
- postMessage: (_msg: WebviewToExtensionMessage) => {},
6
- onMessage: (_handler: (msg: ExtensionToWebviewMessage) => void): (() => void) => () => {},
14
+ postMessage: (msg: WebviewToExtensionMessage) => {
15
+ window.__TLD_VSCODE_API__?.postMessage(msg)
16
+ },
17
+ onMessage: (handler: (msg: ExtensionToWebviewMessage) => void): (() => void) => {
18
+ const listener = (event: MessageEvent) => {
19
+ if (event.data && typeof event.data === 'object' && 'type' in event.data) {
20
+ handler(event.data as ExtensionToWebviewMessage)
21
+ }
22
+ }
23
+ window.addEventListener('message', listener)
24
+ return () => window.removeEventListener('message', listener)
25
+ },
7
26
  }
@@ -8,6 +8,7 @@ declare module "*.svg" {
8
8
  declare global {
9
9
  interface Window {
10
10
  __TLD_VSCODE__?: boolean
11
+ __TLD_SERVER_URL__?: string
11
12
  }
12
13
  }
13
14
 
@@ -19,6 +19,11 @@ export function resolveWithBase(urlOrPath: string): string {
19
19
  if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://') || urlOrPath.startsWith('data:')) {
20
20
  return urlOrPath
21
21
  }
22
+ const vscodeServerUrl = typeof window !== 'undefined' ? window.__TLD_SERVER_URL__?.replace(/\/+$/, '') : undefined
23
+ if (window.__TLD_VSCODE__ && vscodeServerUrl) {
24
+ const normalizedPath = urlOrPath.startsWith('/') ? urlOrPath : `/${urlOrPath}`
25
+ return `${vscodeServerUrl}${normalizedPath}`
26
+ }
22
27
 
23
28
  // When running inside the native mobile app (Capacitor), or inside an embedded webview
24
29
  // that serves content from localhost, avoid prefixing the app BASE_URL. Mobile builds and
package/src/utils/url.ts CHANGED
@@ -9,6 +9,13 @@ export function resolveIconPath(path: string | null | undefined): string {
9
9
 
10
10
  // Absolute URLs and data URIs are returned as-is
11
11
  if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:')) return path
12
+ const vscodeServerUrl = typeof window !== 'undefined' ? window.__TLD_SERVER_URL__?.replace(/\/+$/, '') : undefined
13
+ const isVsCode = typeof window !== 'undefined' && !!window.__TLD_VSCODE__
14
+ if (isVsCode && vscodeServerUrl) {
15
+ const stripped = path.startsWith('/app/') ? path.slice('/app'.length) : path
16
+ const normalizedPath = stripped.startsWith('/') ? stripped : `/${stripped}`
17
+ return `${vscodeServerUrl}${normalizedPath}`
18
+ }
12
19
 
13
20
  // If running inside the native mobile app (Capacitor) OR inside an embedded
14
21
  // webview that serves content from localhost (e.g. Capacitor production webview),
@@ -0,0 +1,56 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { ChakraProvider } from '@chakra-ui/react'
4
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
5
+ import { MemoryRouter } from 'react-router-dom'
6
+ import App from './App'
7
+ import theme from './theme'
8
+ import { ToastContainer } from './utils/toast'
9
+ import { PlatformProvider } from './platform/PlatformContext'
10
+ import { platform as localPlatform } from './platform/local'
11
+ import './index.css'
12
+
13
+ declare global {
14
+ interface Window {
15
+ __TLD_DIAGRAM_ID__?: number
16
+ __TLD_VSCODE__?: boolean
17
+ __TLD_VSCODE_API__?: {
18
+ postMessage: (msg: unknown) => void
19
+ }
20
+ __TLD_SERVER_URL__?: string
21
+ }
22
+ }
23
+
24
+ const diagramId = window.__TLD_DIAGRAM_ID__
25
+ const initialPath = diagramId != null ? `/views/${diagramId}` : '/views'
26
+
27
+ const queryClient = new QueryClient({
28
+ defaultOptions: {
29
+ queries: {
30
+ staleTime: 5_000,
31
+ refetchOnWindowFocus: false,
32
+ },
33
+ },
34
+ })
35
+
36
+ createRoot(document.getElementById('root')!).render(
37
+ <StrictMode>
38
+ <QueryClientProvider client={queryClient}>
39
+ <ChakraProvider theme={theme}>
40
+ <PlatformProvider platform={localPlatform}>
41
+ <MemoryRouter
42
+ initialEntries={[initialPath]}
43
+ future={{
44
+ v7_startTransition: true,
45
+ v7_relativeSplatPath: true,
46
+ }}
47
+ >
48
+ <App />
49
+ </MemoryRouter>
50
+ <ToastContainer />
51
+ </PlatformProvider>
52
+ </ChakraProvider>
53
+ </QueryClientProvider>
54
+ </StrictMode>,
55
+ )
56
+