@plannotator/ui 0.32.0 → 0.34.0
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/HANDOFF.md +697 -0
- package/README.md +17 -5
- package/components/MermaidBlock.tsx +16 -0
- package/components/html-viewer/HtmlViewer.tsx +187 -1
- package/components/html-viewer/bridge-script.asset.js +4392 -0
- package/components/html-viewer/bridge-script.lite.ts +9 -0
- package/components/html-viewer/bridge-script.ts +13 -1
- package/components/html-viewer/index.ts +13 -1
- package/components/html-viewer/srcdoc.ts +70 -1
- package/components/html-viewer/useHtmlAnnotation.ts +37 -0
- package/package.json +6 -2
- package/styles.css +1 -1
- package/utils/math-default-loader.ts +24 -0
- package/utils/math.ts +45 -18
- package/utils/mermaid-math-slot.ts +56 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @generated by scripts/build-bridge-assets.ts from ./bridge-script.ts. DO NOT EDIT.
|
|
2
|
+
// Alias target for hosts on the HtmlViewer `bridgeScriptUrl` path: the same
|
|
3
|
+
// annotation CSS, protocol version and live bootstrap as ./bridge-script, with
|
|
4
|
+
// the inline bridge literal stubbed out so a bundler drops it from the viewer
|
|
5
|
+
// chunk. Rendering an HtmlViewer WITHOUT bridgeScriptUrl under this alias throws.
|
|
6
|
+
export const ANNOTATION_HIGHLIGHT_CSS = "\n/* Committed annotation visuals (highlight rectangles + numbered placed\n * markers) render inside a shadow-rooted fixed overlay host — see OVERLAY_CSS\n * in the bridge script. Nothing annotation-related is ever wrapped into or\n * styled onto the author's own elements. */\n/* Vim pinpoint target tint. The MOUSE pinpoint path no longer mutates author\n * elements — it draws the dedicated overlay box below — but keyboard (vim)\n * navigation keeps this class-based visual. */\n.plannotator-pinpoint-hover {\n background-color: oklch(from var(--pn-focus-highlight, #4493f8) l c h / 0.12) !important;\n border-radius: 3px;\n cursor: pointer !important;\n}\n/* SVG groups can't render a CSS background, so use a soft glow instead. */\n.plannotator-pinpoint-hover:is(g, svg) {\n filter: drop-shadow(0 0 4px oklch(from var(--pn-focus-highlight, #4493f8) l c h / 0.55));\n}\n/* Mouse pinpoint hover: a fixed-position outline box sized to the hovered\n * element's rect. Never a class/style write on the page's own elements. */\n[data-plannotator-pinpoint-box] {\n position: fixed;\n z-index: 2147483643;\n pointer-events: none;\n display: none;\n box-sizing: border-box;\n border: 2px solid oklch(from var(--pn-focus-highlight, #4493f8) l c h / 0.85);\n border-radius: 5px;\n background: oklch(from var(--pn-focus-highlight, #4493f8) l c h / 0.06);\n}\n[data-plannotator-pinpoint-box].pn-pin-enter {\n animation: pn-pinpoint-in 0.12s ease-out;\n}\n[data-plannotator-pinpoint-box][data-pinned] {\n border-color: var(--pn-accent, #d97757);\n background: oklch(from var(--pn-accent, #d97757) l c h / 0.08);\n}\n@keyframes pn-pinpoint-in {\n from { opacity: 0; transform: scale(0.985); }\n to { opacity: 1; transform: scale(1); }\n}\n/* Pinpoint mode affordance: crosshair everywhere. Placed markers live in the\n * shadow overlay and keep their own pointer cursor there. */\nbody[data-plannotator-pinpoint-cursor],\nbody[data-plannotator-pinpoint-cursor] * {\n cursor: crosshair !important;\n}\n@media (prefers-reduced-motion: reduce) {\n [data-plannotator-pinpoint-box].pn-pin-enter {\n animation: none;\n }\n}\n@media print {\n /* Viewer overlays are review chrome, not page content: never bake pinpoint\n boxes/labels or vim UI into a printed page. The outer app chrome is\n print-hidden by print.css, but this CSS lives inside the iframe's own\n document and must carry its own rule. The annotation overlay host carries\n its own print rule inside its shadow root. */\n [data-plannotator-pinpoint-box],\n [data-plannotator-pinpoint-label],\n [data-plannotator-vim-ui],\n [data-plannotator-vim-cursor] {\n display: none !important;\n }\n}\n/* Print-parity layer: committed highlight rects re-projected into an\n * absolute-positioned light-DOM layer built on beforeprint and torn down on\n * afterprint (the fixed overlay cannot paginate). Guarded here so it can\n * never flash on screen even if an afterprint teardown is missed. */\n@media screen {\n [data-plannotator-print-layer] {\n display: none !important;\n }\n}\nbody[data-plannotator-vim-focus-owner]:focus {\n outline: none !important;\n}\n[data-plannotator-vim-cursor] {\n position: fixed;\n z-index: 2147483646;\n width: 2px;\n min-height: 1em;\n border-radius: 2px;\n background: var(--pn-focus-highlight, #4493f8);\n pointer-events: none;\n}\n[data-plannotator-vim-reticle] {\n position: fixed;\n z-index: 2147483645;\n inset: 0;\n overflow: visible;\n pointer-events: none;\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-fill],\n[data-plannotator-vim-reticle] [data-vim-reticle-corner],\n[data-plannotator-vim-reticle] [data-vim-reticle-label] {\n position: absolute;\n top: 0;\n left: 0;\n will-change: transform;\n transition: transform 90ms cubic-bezier(.22,1,.36,1);\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-fill] {\n width: 100px;\n height: 100px;\n transform-origin: 0 0;\n border-radius: 8px;\n background: rgba(167,139,250,.045);\n box-shadow:\n inset 0 0 0 1px rgba(196,181,253,.16),\n 0 0 42px rgba(139,92,246,.12);\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-corner] {\n width: 28px;\n height: 28px;\n border-color: #c4b5fd;\n filter:\n drop-shadow(0 0 6px rgba(167,139,250,.92))\n drop-shadow(0 0 18px rgba(124,58,237,.42));\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-corner=\"top-left\"] {\n border-top: 3px solid;\n border-left: 3px solid;\n border-top-left-radius: 8px;\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-corner=\"top-right\"] {\n border-top: 3px solid;\n border-right: 3px solid;\n border-top-right-radius: 8px;\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-corner=\"bottom-left\"] {\n border-bottom: 3px solid;\n border-left: 3px solid;\n border-bottom-left-radius: 8px;\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-corner=\"bottom-right\"] {\n border-right: 3px solid;\n border-bottom: 3px solid;\n border-bottom-right-radius: 8px;\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-label] {\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 118px;\n height: 30px;\n max-width: min(280px, calc(100vw - 24px));\n padding: 0 11px;\n overflow: hidden;\n border: 1px solid rgba(216,206,255,.42);\n border-radius: 9px;\n color: #f6f2ff;\n background: rgba(18,14,28,.84);\n box-shadow:\n 0 10px 28px rgba(0,0,0,.42),\n 0 0 20px rgba(139,92,246,.18);\n backdrop-filter: blur(10px);\n font: 700 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;\n letter-spacing: .13em;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n[data-plannotator-vim-reticle] [data-vim-reticle-label]::before {\n width: 7px;\n height: 7px;\n flex: 0 0 auto;\n border-radius: 999px;\n background: #c4b5fd;\n box-shadow: 0 0 12px rgba(167,139,250,.94);\n content: \"\";\n}\n@media (prefers-reduced-motion: reduce) {\n [data-plannotator-vim-reticle] [data-vim-reticle-fill],\n [data-plannotator-vim-reticle] [data-vim-reticle-corner],\n [data-plannotator-vim-reticle] [data-vim-reticle-label] {\n transition: none;\n }\n}\n[data-plannotator-vim-badge] {\n position: fixed;\n z-index: 2147483647;\n left: 50%;\n bottom: 12px;\n transform: translateX(-50%);\n padding: 4px 9px;\n border: 1px solid color-mix(in srgb, var(--pn-focus-highlight, #4493f8) 35%, transparent);\n border-radius: 6px;\n background: color-mix(in srgb, var(--pn-background, #111) 94%, transparent);\n color: var(--pn-focus-highlight, #4493f8);\n box-shadow: 0 4px 18px rgba(0,0,0,.25);\n font: 700 10px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;\n letter-spacing: .04em;\n pointer-events: none;\n}\n";
|
|
7
|
+
export const BRIDGE_PROTOCOL_VERSION = 1;
|
|
8
|
+
export const BRIDGE_SCRIPT = "";
|
|
9
|
+
export const LIVE_BRIDGE_BOOTSTRAP = "(function() {\n var config = window.__plannotatorLiveConfig;\n if (!config || typeof config.css !== 'string') return;\n try {\n var style = document.createElement('style');\n style.setAttribute('data-plannotator-live-css', '');\n style.appendChild(document.createTextNode(config.css));\n (document.head || document.documentElement).appendChild(style);\n } catch (ex) {}\n})();";
|
|
@@ -210,6 +210,18 @@ body[data-plannotator-vim-focus-owner]:focus {
|
|
|
210
210
|
}
|
|
211
211
|
`;
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Bridge protocol version. Stamped on the bridge's `ready` message
|
|
215
|
+
* (`protocolVersion`) and compared by the parent (HtmlViewer) against this
|
|
216
|
+
* same constant. Bump it whenever a message shape changes in a way an older
|
|
217
|
+
* bridge or an older parent would misread. The inline srcdoc path and the
|
|
218
|
+
* live proxy always ship the bridge from the same bundle as the parent, so
|
|
219
|
+
* they match by construction; the check exists for hosts that serve the
|
|
220
|
+
* generated `bridge-script.asset.js` separately (`bridgeScriptUrl`), where a
|
|
221
|
+
* cached asset from a previous package version can outlive the parent code.
|
|
222
|
+
*/
|
|
223
|
+
export const BRIDGE_PROTOCOL_VERSION = 1;
|
|
224
|
+
|
|
213
225
|
export const BRIDGE_SCRIPT = `(function() {
|
|
214
226
|
var PREFIX = 'plannotator-bridge-';
|
|
215
227
|
|
|
@@ -4572,7 +4584,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
4572
4584
|
// pinpoint: show the cursor affordance immediately instead of waiting for
|
|
4573
4585
|
// the parent's first set-input-method/set-annotate-mode round trip.
|
|
4574
4586
|
updatePinpointCursor();
|
|
4575
|
-
var readyMsg = { type: PREFIX + 'ready' };
|
|
4587
|
+
var readyMsg = { type: PREFIX + 'ready', protocolVersion: ${BRIDGE_PROTOCOL_VERSION} };
|
|
4576
4588
|
if (LIVE) readyMsg.pageUrl = currentPageUrl();
|
|
4577
4589
|
postToParent(readyMsg);
|
|
4578
4590
|
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
DEFAULT_BRIDGE_READY_TIMEOUT_MS,
|
|
3
|
+
HtmlViewer,
|
|
4
|
+
formatBridgeUnavailableMessage,
|
|
5
|
+
type BridgeUnavailableInfo,
|
|
6
|
+
type HtmlViewerProps,
|
|
7
|
+
} from "./HtmlViewer";
|
|
8
|
+
export { BRIDGE_PROTOCOL_VERSION } from "./bridge-script";
|
|
9
|
+
export {
|
|
10
|
+
checkBridgeProtocolVersion,
|
|
11
|
+
formatBridgeProtocolWarning,
|
|
12
|
+
type BridgeProtocolVerdict,
|
|
13
|
+
} from "./useHtmlAnnotation";
|
|
2
14
|
export {
|
|
3
15
|
buildPersistedHtmlAnchor,
|
|
4
16
|
projectHostThreads,
|
|
@@ -96,6 +96,68 @@ export interface SrcdocInjectionOptions {
|
|
|
96
96
|
hostTheme: boolean;
|
|
97
97
|
/** The version-diff view is showing (rawHtml is htmlDiff output). */
|
|
98
98
|
diffActive: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Load the bridge through a classic `<script src>` from this URL instead of
|
|
101
|
+
* inlining `BRIDGE_SCRIPT`. Absent or empty: inline, exactly as before. The
|
|
102
|
+
* tag takes the inline script's place, so placement is identical on both
|
|
103
|
+
* paths: at the end of `<head>`, before the body (head scripts of the page
|
|
104
|
+
* run first, body scripts after the bridge). No `crossorigin` attribute is
|
|
105
|
+
* set: the srcdoc frame is an opaque origin, and a classic script needs no
|
|
106
|
+
* CORS to execute. Callers must pass an ABSOLUTE URL (see
|
|
107
|
+
* {@link resolveBridgeScriptUrl}): the framed page may carry its own
|
|
108
|
+
* `<base href>`, which precedes the injected tag and would re-anchor a
|
|
109
|
+
* relative URL onto an attacker-chosen origin.
|
|
110
|
+
*/
|
|
111
|
+
bridgeScriptUrl?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolve a host-supplied bridge URL against the PARENT document (its
|
|
116
|
+
* `document.baseURI`), never against the framed page. The injection lands at
|
|
117
|
+
* the end of the page's `<head>`, after any `<base href>` the page declares,
|
|
118
|
+
* so a relative URL written into the srcdoc would resolve against that base:
|
|
119
|
+
* a hostile document could point the viewer at an attacker-served bridge and
|
|
120
|
+
* defeat the version check. Resolving here pins the URL before it is written.
|
|
121
|
+
* An unparsable input is returned unchanged (nothing loads, the ready timeout
|
|
122
|
+
* reports it) rather than throwing during render.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveBridgeScriptUrl(bridgeScriptUrl: string, parentBaseUrl: string): string {
|
|
125
|
+
try {
|
|
126
|
+
return new URL(bridgeScriptUrl, parentBaseUrl).href;
|
|
127
|
+
} catch {
|
|
128
|
+
return bridgeScriptUrl;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Escape a string for a double-quoted HTML attribute value. */
|
|
133
|
+
function escapeAttribute(value: string): string {
|
|
134
|
+
return value
|
|
135
|
+
.replace(/&/g, "&")
|
|
136
|
+
.replace(/"/g, """)
|
|
137
|
+
.replace(/</g, "<")
|
|
138
|
+
.replace(/>/g, ">");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The bridge `<script>` element, the ONE injection point for both delivery
|
|
143
|
+
* paths. Inline is the default and Plannotator's only path; the URL form is
|
|
144
|
+
* the opt-in for hosts that serve the generated `bridge-script.asset.js`.
|
|
145
|
+
*/
|
|
146
|
+
export function buildBridgeScriptTag(bridgeScriptUrl?: string): string {
|
|
147
|
+
if (bridgeScriptUrl) {
|
|
148
|
+
return `<script src="${escapeAttribute(bridgeScriptUrl)}"></script>`;
|
|
149
|
+
}
|
|
150
|
+
if (!BRIDGE_SCRIPT) {
|
|
151
|
+
// Only reachable when a host aliased `./bridge-script` to the generated
|
|
152
|
+
// `bridge-script.lite` module (which stubs the inline literal) and then
|
|
153
|
+
// rendered an HtmlViewer without `bridgeScriptUrl`: an empty inline
|
|
154
|
+
// script would be a silently dead surface, so fail loudly instead.
|
|
155
|
+
throw new Error(
|
|
156
|
+
"@plannotator/ui HtmlViewer: the inline bridge script is stubbed out "
|
|
157
|
+
+ "(bridge-script.lite alias) but no bridgeScriptUrl was passed.",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return `<script>${BRIDGE_SCRIPT}</script>`;
|
|
99
161
|
}
|
|
100
162
|
|
|
101
163
|
/** The `<style>` + `<script>` block spliced into the document's head. */
|
|
@@ -104,6 +166,7 @@ export function buildSrcdocInjection({
|
|
|
104
166
|
isLight,
|
|
105
167
|
hostTheme,
|
|
106
168
|
diffActive,
|
|
169
|
+
bridgeScriptUrl,
|
|
107
170
|
}: SrcdocInjectionOptions): string {
|
|
108
171
|
const payload = buildThemeTokenPayload(tokens, hostTheme);
|
|
109
172
|
let themeCSS = ":root {\n";
|
|
@@ -117,7 +180,7 @@ export function buildSrcdocInjection({
|
|
|
117
180
|
themeCSS += `:root { color-scheme: ${isLight ? "light" : "dark"}; }\n`;
|
|
118
181
|
}
|
|
119
182
|
const diffCSS = diffActive ? DIFF_HIGHLIGHT_CSS : "";
|
|
120
|
-
return `<style>${themeCSS}${ANNOTATION_HIGHLIGHT_CSS}${diffCSS}</style
|
|
183
|
+
return `<style>${themeCSS}${ANNOTATION_HIGHLIGHT_CSS}${diffCSS}</style>${buildBridgeScriptTag(bridgeScriptUrl)}`;
|
|
121
184
|
}
|
|
122
185
|
|
|
123
186
|
/**
|
|
@@ -126,6 +189,12 @@ export function buildSrcdocInjection({
|
|
|
126
189
|
* script and disables annotation entirely. The iframe `sandbox` attribute is
|
|
127
190
|
* the security boundary for the annotate surface; the page's CSP was written
|
|
128
191
|
* for its standalone context, so it is removed before injection.
|
|
192
|
+
*
|
|
193
|
+
* The package itself never adds a CSP `<meta>` to the srcdoc document (the
|
|
194
|
+
* injection is one `<style>` and one `<script>`), so nothing here blocks a
|
|
195
|
+
* `<script src>` on the URL path. A CSP delivered as an HTTP header on the
|
|
196
|
+
* HOST page is inherited by the srcdoc document, and the host must allow
|
|
197
|
+
* `script-src` for the origin the asset is served from.
|
|
129
198
|
*/
|
|
130
199
|
const META_CSP_RE =
|
|
131
200
|
/<meta\s[^>]*http-equiv\s*=\s*["']?\s*content-security-policy\s*["']?[^>]*\/?>/gi;
|
|
@@ -8,9 +8,46 @@ import type {
|
|
|
8
8
|
QuickLabelPickerState,
|
|
9
9
|
UseAnnotationHighlighterReturn,
|
|
10
10
|
} from "../../hooks/useAnnotationHighlighter";
|
|
11
|
+
import { BRIDGE_PROTOCOL_VERSION } from "./bridge-script";
|
|
11
12
|
|
|
12
13
|
const PREFIX = "plannotator-bridge-";
|
|
13
14
|
|
|
15
|
+
/** Outcome of comparing a bridge `ready` message's stamp with this parent. */
|
|
16
|
+
export interface BridgeProtocolVerdict {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
/** The version this parent bundle speaks (`BRIDGE_PROTOCOL_VERSION`). */
|
|
19
|
+
expected: number;
|
|
20
|
+
/** The version the bridge reported; `undefined` when the ready carried none
|
|
21
|
+
* (a bridge asset built before the stamp existed, or a forged message). */
|
|
22
|
+
reported: number | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compare a `ready` message against the parent's protocol version. A missing
|
|
27
|
+
* stamp counts as a mismatch: the only way it can be absent is a bridge asset
|
|
28
|
+
* older than the parent (or a page forging the message), which is exactly
|
|
29
|
+
* the drift this check exists to name.
|
|
30
|
+
*/
|
|
31
|
+
export function checkBridgeProtocolVersion(data: unknown): BridgeProtocolVerdict {
|
|
32
|
+
const raw = isRecord(data) ? data.protocolVersion : undefined;
|
|
33
|
+
const reported = typeof raw === "number" && Number.isFinite(raw) ? raw : undefined;
|
|
34
|
+
return {
|
|
35
|
+
ok: reported === BRIDGE_PROTOCOL_VERSION,
|
|
36
|
+
expected: BRIDGE_PROTOCOL_VERSION,
|
|
37
|
+
reported,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The one console warning a mismatch produces; names both versions. */
|
|
42
|
+
export function formatBridgeProtocolWarning(
|
|
43
|
+
verdict: BridgeProtocolVerdict,
|
|
44
|
+
bridgeScriptUrl?: string,
|
|
45
|
+
): string {
|
|
46
|
+
const reported = verdict.reported === undefined ? "none" : String(verdict.reported);
|
|
47
|
+
const source = bridgeScriptUrl ? `the bridge script at ${bridgeScriptUrl}` : "the bridge script";
|
|
48
|
+
return `[plannotator] HTML bridge protocol version mismatch: this viewer expects ${verdict.expected}, ${source} reported ${reported}. Serve the bridge-script asset from the same @plannotator/ui version as the viewer.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
14
51
|
// Collision-proof annotation ids. `Date.now()` alone repeats within a millisecond,
|
|
15
52
|
// so two quick annotations could share a data-bind-id and clobber each other.
|
|
16
53
|
let htmlAnnSeq = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./components/*": "./components/*.tsx",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"./components/ImageAnnotator": "./components/ImageAnnotator/index.tsx",
|
|
12
12
|
"./components/html-viewer": "./components/html-viewer/index.ts",
|
|
13
13
|
"./components/html-viewer/bridge-script": "./components/html-viewer/bridge-script.ts",
|
|
14
|
+
"./components/html-viewer/bridge-script.asset.js": "./components/html-viewer/bridge-script.asset.js",
|
|
15
|
+
"./components/html-viewer/bridge-script.lite": "./components/html-viewer/bridge-script.lite.ts",
|
|
14
16
|
"./components/sidebar/*": "./components/sidebar/*.tsx",
|
|
15
17
|
"./components/plan-diff/*": "./components/plan-diff/*.tsx",
|
|
16
18
|
"./utils/*": "./utils/*.ts",
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
"print.css",
|
|
47
49
|
"styles.css",
|
|
48
50
|
"plannotator.webp",
|
|
51
|
+
"HANDOFF.md",
|
|
49
52
|
"!**/*.test.ts",
|
|
50
53
|
"!**/*.test.tsx",
|
|
51
54
|
"!test-setup"
|
|
@@ -109,6 +112,7 @@
|
|
|
109
112
|
"scripts": {
|
|
110
113
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
111
114
|
"build:css": "vite build --config vite.css.config.ts && rm -f styles.js",
|
|
112
|
-
"
|
|
115
|
+
"build:bridge-assets": "bun run scripts/build-bridge-assets.ts",
|
|
116
|
+
"prepack": "bun run build:css && bun run build:bridge-assets"
|
|
113
117
|
}
|
|
114
118
|
}
|