pilotswarm 0.5.16 → 0.5.18
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/mcp/dist/src/tools/capabilities.d.ts +3 -0
- package/mcp/dist/src/tools/capabilities.d.ts.map +1 -1
- package/mcp/dist/src/tools/capabilities.js +4 -0
- package/mcp/dist/src/tools/capabilities.js.map +1 -1
- package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
- package/mcp/dist/src/tools/sessions.js +46 -6
- package/mcp/dist/src/tools/sessions.js.map +1 -1
- package/package.json +2 -2
- package/tui/src/app.js +10 -0
- package/tui/src/node-sdk-transport.js +103 -0
- package/ui/core/src/controller.js +205 -4
- package/ui/core/src/formatting.js +4 -1
- package/ui/core/src/history.js +6 -0
- package/ui/core/src/reducer.js +72 -0
- package/ui/core/src/selectors.js +31 -2
- package/ui/react/src/web-app.js +319 -20
- package/web/api/router.js +7 -0
- package/web/dist/assets/index-CxkLM8_a.css +1 -0
- package/web/dist/assets/{index-Df1mD9p0.js → index-DdPfXk16.js} +2 -2
- package/web/dist/assets/pilotswarm-DgoJR1qM.js +90 -0
- package/web/dist/index.html +3 -3
- package/web/server.js +5 -0
- package/web/dist/assets/index-whIft7M6.css +0 -1
- package/web/dist/assets/pilotswarm-DR5j-z8y.js +0 -90
package/ui/react/src/web-app.js
CHANGED
|
@@ -423,8 +423,9 @@ function normalizeLines(lines) {
|
|
|
423
423
|
}
|
|
424
424
|
// Sentinel kinds preserved as-is so parseStructuredChatBlocks can
|
|
425
425
|
// recognize and render them (e.g. markdownTable → HTML <table>,
|
|
426
|
-
// cardStart/cardEnd → styled card with structured body
|
|
427
|
-
|
|
426
|
+
// cardStart/cardEnd → styled card with structured body,
|
|
427
|
+
// imageAttachments → authenticated thumbnail strip).
|
|
428
|
+
if (line?.kind === "markdownTable" || line?.kind === "cardStart" || line?.kind === "cardEnd" || line?.kind === "imageAttachments") {
|
|
428
429
|
normalized.push(line);
|
|
429
430
|
continue;
|
|
430
431
|
}
|
|
@@ -594,6 +595,23 @@ export function isScrollViewportAtBottom(node) {
|
|
|
594
595
|
return getScrollDistanceToBottom(node) <= SCROLL_BOTTOM_EPSILON_PX;
|
|
595
596
|
}
|
|
596
597
|
|
|
598
|
+
export function computeAnchoredScrollTop(
|
|
599
|
+
node,
|
|
600
|
+
scrollOffset,
|
|
601
|
+
scrollMode,
|
|
602
|
+
preservePausedStickyScroll = false,
|
|
603
|
+
) {
|
|
604
|
+
if (!node) return 0;
|
|
605
|
+
const maxScroll = Math.max(0, node.scrollHeight - node.clientHeight);
|
|
606
|
+
if (preservePausedStickyScroll) {
|
|
607
|
+
return Math.max(0, Math.min(node.scrollTop, maxScroll));
|
|
608
|
+
}
|
|
609
|
+
const offsetPixels = Math.max(0, Number(scrollOffset) || 0) * SCROLL_ROW_HEIGHT;
|
|
610
|
+
return scrollMode === "bottom"
|
|
611
|
+
? Math.max(0, maxScroll - offsetPixels)
|
|
612
|
+
: Math.min(maxScroll, offsetPixels);
|
|
613
|
+
}
|
|
614
|
+
|
|
597
615
|
function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller, { stickyBottom = false } = {}) {
|
|
598
616
|
const normalizedLines = React.useMemo(() => normalizeLines(lines), [lines]);
|
|
599
617
|
// Programmatic scrollTop assignments fire a 'scroll' event that would
|
|
@@ -613,6 +631,23 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
|
|
|
613
631
|
// or its momentum is in flight, the DOM is the source of truth and state
|
|
614
632
|
// echoes of our own scroll dispatches must not snap the pane back.
|
|
615
633
|
const userScrollRef = React.useRef({ touching: false, lastUserScrollAt: 0, lastDispatchedOffset: null });
|
|
634
|
+
const viewportSizeRef = React.useRef({ width: null, height: null });
|
|
635
|
+
const [viewportRevision, setViewportRevision] = React.useState(0);
|
|
636
|
+
|
|
637
|
+
React.useLayoutEffect(() => {
|
|
638
|
+
const node = ref.current;
|
|
639
|
+
if (!node || typeof ResizeObserver === "undefined") return;
|
|
640
|
+
viewportSizeRef.current = { width: node.clientWidth, height: node.clientHeight };
|
|
641
|
+
const observer = new ResizeObserver(() => {
|
|
642
|
+
const next = { width: node.clientWidth, height: node.clientHeight };
|
|
643
|
+
const previous = viewportSizeRef.current;
|
|
644
|
+
if (next.width === previous.width && next.height === previous.height) return;
|
|
645
|
+
viewportSizeRef.current = next;
|
|
646
|
+
setViewportRevision((revision) => revision + 1);
|
|
647
|
+
});
|
|
648
|
+
observer.observe(node);
|
|
649
|
+
return () => observer.disconnect();
|
|
650
|
+
}, [ref]);
|
|
616
651
|
|
|
617
652
|
React.useLayoutEffect(() => {
|
|
618
653
|
const node = ref.current;
|
|
@@ -632,17 +667,16 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
|
|
|
632
667
|
previousViewportStateRef.current = { scrollMode, scrollOffset };
|
|
633
668
|
return;
|
|
634
669
|
}
|
|
635
|
-
const maxScroll = Math.max(0, node.scrollHeight - node.clientHeight);
|
|
636
670
|
const preservePausedStickyScroll = stickyBottom
|
|
637
671
|
&& scrollMode === "top"
|
|
638
672
|
&& previousViewportState?.scrollMode === "top"
|
|
639
673
|
&& previousViewportState?.scrollOffset === scrollOffset;
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
674
|
+
const nextScrollTop = computeAnchoredScrollTop(
|
|
675
|
+
node,
|
|
676
|
+
scrollOffset,
|
|
677
|
+
scrollMode,
|
|
678
|
+
preservePausedStickyScroll,
|
|
679
|
+
);
|
|
646
680
|
if (Math.abs(node.scrollTop - nextScrollTop) > PROGRAMMATIC_SCROLL_TOLERANCE_PX) {
|
|
647
681
|
const pendingProgrammaticScroll = { target: nextScrollTop };
|
|
648
682
|
programmaticScrollRef.current = pendingProgrammaticScroll;
|
|
@@ -659,7 +693,7 @@ function useScrollSync(ref, lines, scrollOffset, scrollMode, paneKey, controller
|
|
|
659
693
|
scrollMode,
|
|
660
694
|
scrollOffset,
|
|
661
695
|
};
|
|
662
|
-
}, [normalizedLines, ref, scrollMode, scrollOffset, stickyBottom]);
|
|
696
|
+
}, [normalizedLines, ref, scrollMode, scrollOffset, stickyBottom, viewportRevision]);
|
|
663
697
|
|
|
664
698
|
const onScroll = React.useCallback(() => {
|
|
665
699
|
const node = ref.current;
|
|
@@ -1258,7 +1292,7 @@ function formatArtifactPreviewBytes(value) {
|
|
|
1258
1292
|
return `${mb >= 10 ? Math.round(mb) : mb.toFixed(1)} MB`;
|
|
1259
1293
|
}
|
|
1260
1294
|
|
|
1261
|
-
function BinaryArtifactPreviewPanel({ title, color, focused, theme, filename, contentType, sizeBytes, source, uploadedAt }) {
|
|
1295
|
+
function BinaryArtifactPreviewPanel({ title, color, focused, theme, filename, contentType, sizeBytes, source, uploadedAt, controller = null, sessionId = null }) {
|
|
1262
1296
|
const meta = [];
|
|
1263
1297
|
if (contentType) meta.push(contentType);
|
|
1264
1298
|
if (sizeBytes != null) meta.push(formatArtifactPreviewBytes(sizeBytes));
|
|
@@ -1273,9 +1307,23 @@ function BinaryArtifactPreviewPanel({ title, color, focused, theme, filename, co
|
|
|
1273
1307
|
})
|
|
1274
1308
|
: "";
|
|
1275
1309
|
|
|
1310
|
+
// Raster images render inline (authenticated fetch → object URL). Other
|
|
1311
|
+
// binary types keep the download-only card.
|
|
1312
|
+
const isRasterImage = /^image\/(png|jpeg|gif|webp)$/i.test(String(contentType || ""));
|
|
1313
|
+
const [imageUrl, setImageUrl] = React.useState(null);
|
|
1314
|
+
React.useEffect(() => {
|
|
1315
|
+
setImageUrl(null);
|
|
1316
|
+
if (!isRasterImage || !controller || !sessionId || !filename) return undefined;
|
|
1317
|
+
let cancelled = false;
|
|
1318
|
+
fetchArtifactObjectUrl(controller, sessionId, filename)
|
|
1319
|
+
.then((url) => { if (!cancelled) setImageUrl(url); })
|
|
1320
|
+
.catch(() => { if (!cancelled) setImageUrl("error"); });
|
|
1321
|
+
return () => { cancelled = true; };
|
|
1322
|
+
}, [isRasterImage, controller, sessionId, filename]);
|
|
1323
|
+
|
|
1276
1324
|
return React.createElement(Panel, { title, color, focused, theme },
|
|
1277
1325
|
React.createElement("div", { className: "ps-binary-preview-card" },
|
|
1278
|
-
React.createElement("div", { className: "ps-binary-preview-kicker" }, "Binary artifact"),
|
|
1326
|
+
React.createElement("div", { className: "ps-binary-preview-kicker" }, isRasterImage ? "Image artifact" : "Binary artifact"),
|
|
1279
1327
|
React.createElement("h3", { className: "ps-binary-preview-title" }, filename || "Artifact"),
|
|
1280
1328
|
meta.length > 0
|
|
1281
1329
|
? React.createElement("div", { className: "ps-binary-preview-meta" }, meta.join(" • "))
|
|
@@ -1283,8 +1331,17 @@ function BinaryArtifactPreviewPanel({ title, color, focused, theme, filename, co
|
|
|
1283
1331
|
uploadedLabel
|
|
1284
1332
|
? React.createElement("div", { className: "ps-binary-preview-time" }, `Uploaded ${uploadedLabel}`)
|
|
1285
1333
|
: null,
|
|
1286
|
-
|
|
1287
|
-
|
|
1334
|
+
isRasterImage && imageUrl && imageUrl !== "error"
|
|
1335
|
+
? React.createElement("img", {
|
|
1336
|
+
className: "ps-binary-preview-image",
|
|
1337
|
+
src: imageUrl,
|
|
1338
|
+
alt: filename || "artifact image",
|
|
1339
|
+
onClick: () => { try { window.open(imageUrl, "_blank", "noopener"); } catch { /* popup blocked */ } },
|
|
1340
|
+
})
|
|
1341
|
+
: React.createElement("p", { className: "ps-binary-preview-copy" },
|
|
1342
|
+
isRasterImage
|
|
1343
|
+
? (imageUrl === "error" ? "Could not load the image preview. Use Download to save the file." : "Loading image preview…")
|
|
1344
|
+
: "Preview is intentionally disabled for non-text artifacts in the browser workspace. Use Download to save the file and open it in the default app.")));
|
|
1288
1345
|
}
|
|
1289
1346
|
|
|
1290
1347
|
function isBoxTopLine(text) {
|
|
@@ -1440,6 +1497,19 @@ function parseStructuredChatBlocks(lines = []) {
|
|
|
1440
1497
|
continue;
|
|
1441
1498
|
}
|
|
1442
1499
|
|
|
1500
|
+
// Sentinel image-attachment line — a user message's image refs. The
|
|
1501
|
+
// block component fetches bytes through the authenticated transport
|
|
1502
|
+
// (a bare <img src=download-url> cannot carry the Bearer token).
|
|
1503
|
+
if (currentLine?.kind === "imageAttachments") {
|
|
1504
|
+
blocks.push({
|
|
1505
|
+
type: "imageAttachments",
|
|
1506
|
+
sessionId: currentLine.sessionId || null,
|
|
1507
|
+
attachments: Array.isArray(currentLine.attachments) ? currentLine.attachments : [],
|
|
1508
|
+
});
|
|
1509
|
+
index += 1;
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1443
1513
|
// Sentinel markdown-table line emitted by parseMarkdownLines when
|
|
1444
1514
|
// tableMode === "sentinel". Carries the raw header + rows so the
|
|
1445
1515
|
// portal renders a real HTML table with markdown cell content (so
|
|
@@ -1586,17 +1656,105 @@ function parseStructuredChatBlocks(lines = []) {
|
|
|
1586
1656
|
return blocks;
|
|
1587
1657
|
}
|
|
1588
1658
|
|
|
1589
|
-
function StructuredChatBlocks({ lines, theme }) {
|
|
1659
|
+
function StructuredChatBlocks({ lines, theme, controller = null }) {
|
|
1590
1660
|
const blocks = React.useMemo(() => parseStructuredChatBlocks(lines), [lines]);
|
|
1591
|
-
return React.createElement(StructuredBlockList, { blocks, theme });
|
|
1661
|
+
return React.createElement(StructuredBlockList, { blocks, theme, controller });
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// Authenticated artifact thumbnails: bytes are fetched through the transport
|
|
1665
|
+
// (Bearer token) and served to <img> as object URLs. Module-level cache so
|
|
1666
|
+
// scrolling the transcript doesn't refetch; oldest entries are revoked at cap.
|
|
1667
|
+
const ARTIFACT_THUMB_CACHE = new Map();
|
|
1668
|
+
const ARTIFACT_THUMB_CACHE_MAX = 60;
|
|
1669
|
+
|
|
1670
|
+
function fetchArtifactObjectUrl(controller, sessionId, filename) {
|
|
1671
|
+
const key = `${sessionId}/${filename}`;
|
|
1672
|
+
const cached = ARTIFACT_THUMB_CACHE.get(key);
|
|
1673
|
+
if (cached) {
|
|
1674
|
+
// Refresh LRU position.
|
|
1675
|
+
ARTIFACT_THUMB_CACHE.delete(key);
|
|
1676
|
+
ARTIFACT_THUMB_CACHE.set(key, cached);
|
|
1677
|
+
return cached;
|
|
1678
|
+
}
|
|
1679
|
+
const downloadResponse = controller?.transport?.api?.downloadArtifactResponse;
|
|
1680
|
+
if (typeof downloadResponse !== "function") {
|
|
1681
|
+
return Promise.reject(new Error("artifact download unavailable"));
|
|
1682
|
+
}
|
|
1683
|
+
const promise = downloadResponse.call(controller.transport.api, sessionId, filename)
|
|
1684
|
+
.then(async (response) => {
|
|
1685
|
+
if (!response?.ok) throw new Error(`download failed (${response?.status})`);
|
|
1686
|
+
const blob = await response.blob();
|
|
1687
|
+
return URL.createObjectURL(blob);
|
|
1688
|
+
})
|
|
1689
|
+
.catch((error) => {
|
|
1690
|
+
// Failed fetches must not be cached — a retry on next render is fine.
|
|
1691
|
+
if (ARTIFACT_THUMB_CACHE.get(key) === promise) ARTIFACT_THUMB_CACHE.delete(key);
|
|
1692
|
+
throw error;
|
|
1693
|
+
});
|
|
1694
|
+
ARTIFACT_THUMB_CACHE.set(key, promise);
|
|
1695
|
+
while (ARTIFACT_THUMB_CACHE.size > ARTIFACT_THUMB_CACHE_MAX) {
|
|
1696
|
+
const [oldestKey, oldest] = ARTIFACT_THUMB_CACHE.entries().next().value;
|
|
1697
|
+
ARTIFACT_THUMB_CACHE.delete(oldestKey);
|
|
1698
|
+
Promise.resolve(oldest).then((url) => URL.revokeObjectURL(url)).catch(() => {});
|
|
1699
|
+
}
|
|
1700
|
+
return promise;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function ArtifactImageStrip({ controller, sessionId, attachments }) {
|
|
1704
|
+
const [urls, setUrls] = React.useState({});
|
|
1705
|
+
React.useEffect(() => {
|
|
1706
|
+
if (!controller || !sessionId) return undefined;
|
|
1707
|
+
let cancelled = false;
|
|
1708
|
+
for (const attachment of attachments) {
|
|
1709
|
+
const filename = attachment?.filename;
|
|
1710
|
+
if (!filename) continue;
|
|
1711
|
+
fetchArtifactObjectUrl(controller, sessionId, filename)
|
|
1712
|
+
.then((url) => {
|
|
1713
|
+
if (!cancelled) setUrls((prev) => (prev[filename] === url ? prev : { ...prev, [filename]: url }));
|
|
1714
|
+
})
|
|
1715
|
+
.catch(() => {
|
|
1716
|
+
if (!cancelled) setUrls((prev) => (prev[filename] === "error" ? prev : { ...prev, [filename]: "error" }));
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
return () => { cancelled = true; };
|
|
1720
|
+
}, [controller, sessionId, attachments]);
|
|
1721
|
+
|
|
1722
|
+
return React.createElement("div", { className: "ps-chat-image-strip" },
|
|
1723
|
+
attachments.map((attachment, index) => {
|
|
1724
|
+
const filename = attachment?.filename || `image-${index}`;
|
|
1725
|
+
const url = urls[filename];
|
|
1726
|
+
if (url && url !== "error") {
|
|
1727
|
+
return React.createElement("img", {
|
|
1728
|
+
key: `thumb:${index}:${filename}`,
|
|
1729
|
+
className: "ps-chat-image-thumb",
|
|
1730
|
+
src: url,
|
|
1731
|
+
alt: filename,
|
|
1732
|
+
title: `${filename} — click to view full size`,
|
|
1733
|
+
onClick: () => { try { window.open(url, "_blank", "noopener"); } catch { /* popup blocked */ } },
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
return React.createElement("div", {
|
|
1737
|
+
key: `thumb:${index}:${filename}`,
|
|
1738
|
+
className: `ps-chat-image-thumb is-pending${url === "error" ? " is-error" : ""}`,
|
|
1739
|
+
title: filename,
|
|
1740
|
+
}, url === "error" ? "⚠" : "🖼");
|
|
1741
|
+
}));
|
|
1592
1742
|
}
|
|
1593
1743
|
|
|
1594
1744
|
// Renders parsed chat blocks; sentinel card blocks recurse through this list
|
|
1595
1745
|
// so structured content (box/markdown tables, code fences) inside a card
|
|
1596
1746
|
// renders exactly the same as it does at top level.
|
|
1597
|
-
function StructuredBlockList({ blocks, theme }) {
|
|
1747
|
+
function StructuredBlockList({ blocks, theme, controller = null }) {
|
|
1598
1748
|
return React.createElement(React.Fragment, null,
|
|
1599
1749
|
(blocks || []).map((block, index) => {
|
|
1750
|
+
if (block.type === "imageAttachments") {
|
|
1751
|
+
return React.createElement(ArtifactImageStrip, {
|
|
1752
|
+
key: `imageAttachments:${index}`,
|
|
1753
|
+
controller,
|
|
1754
|
+
sessionId: block.sessionId,
|
|
1755
|
+
attachments: block.attachments,
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1600
1758
|
if (block.type === "preserve") {
|
|
1601
1759
|
const variantClass = block.splashVariant ? ` is-splash-${block.splashVariant}` : "";
|
|
1602
1760
|
return React.createElement("div", { key: `preserve:${index}`, className: `ps-chat-preserve-block${variantClass}` },
|
|
@@ -1625,7 +1783,7 @@ function StructuredBlockList({ blocks, theme }) {
|
|
|
1625
1783
|
React.createElement(Runs, { runs: block.headerRuns, theme })),
|
|
1626
1784
|
React.createElement("div", { className: "ps-chat-card-body" },
|
|
1627
1785
|
Array.isArray(block.blocks)
|
|
1628
|
-
? React.createElement(StructuredBlockList, { blocks: block.blocks, theme })
|
|
1786
|
+
? React.createElement(StructuredBlockList, { blocks: block.blocks, theme, controller })
|
|
1629
1787
|
: (block.bodyLines || []).map((bodyRuns, bodyIndex) => React.createElement("div", {
|
|
1630
1788
|
key: `card:${index}:line:${bodyIndex}`,
|
|
1631
1789
|
className: "ps-chat-card-line",
|
|
@@ -1885,7 +2043,7 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
|
|
|
1885
2043
|
typeof renderBody === "function"
|
|
1886
2044
|
? renderBody(normalizedLines, theme)
|
|
1887
2045
|
: structuredBlocks
|
|
1888
|
-
? React.createElement(StructuredChatBlocks, { lines: normalizedLines, theme })
|
|
2046
|
+
? React.createElement(StructuredChatBlocks, { lines: normalizedLines, theme, controller })
|
|
1889
2047
|
: normalizedLines.map((line, index) => React.createElement(Line, { key: `line:${index}`, line, theme })),
|
|
1890
2048
|
),
|
|
1891
2049
|
normalizedBottomSticky.length > 0
|
|
@@ -2953,6 +3111,8 @@ function FilesPane({ controller, focused, mobile = false }) {
|
|
|
2953
3111
|
sizeBytes: filesView.previewSizeBytes,
|
|
2954
3112
|
source: filesView.previewSource,
|
|
2955
3113
|
uploadedAt: filesView.previewUploadedAt,
|
|
3114
|
+
controller,
|
|
3115
|
+
sessionId: filesView.selectedSessionId,
|
|
2956
3116
|
})
|
|
2957
3117
|
: React.createElement(ScrollLinesPanel, {
|
|
2958
3118
|
controller,
|
|
@@ -3295,6 +3455,15 @@ function ChatFocusWorkspace({ controller, openPane, onTogglePane, onExitFocus, m
|
|
|
3295
3455
|
})));
|
|
3296
3456
|
}
|
|
3297
3457
|
|
|
3458
|
+
const EMPTY_ARRAY = Object.freeze([]);
|
|
3459
|
+
|
|
3460
|
+
function formatAttachmentSize(sizeBytes) {
|
|
3461
|
+
const bytes = Number(sizeBytes) || 0;
|
|
3462
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3463
|
+
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
3464
|
+
return `${bytes} B`;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3298
3467
|
function PromptComposer({ controller, mobile, active = true, onAfterSend = null }) {
|
|
3299
3468
|
const promptState = useControllerSelector(controller, (state) => {
|
|
3300
3469
|
const activeSessionId = state.sessions.activeSessionId;
|
|
@@ -3316,9 +3485,54 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null
|
|
|
3316
3485
|
selectedOutboxPhase: state.ui.promptEdit?.sessionId === activeSessionId
|
|
3317
3486
|
? state.ui.promptEdit?.phase || null
|
|
3318
3487
|
: null,
|
|
3488
|
+
// Raw reference (stable across renders) — image entries are
|
|
3489
|
+
// filtered at render time so the shallow-equal selector holds.
|
|
3490
|
+
promptAttachments: state.ui.promptAttachments || EMPTY_ARRAY,
|
|
3319
3491
|
};
|
|
3320
3492
|
}, shallowEqualObject);
|
|
3321
3493
|
const inputRef = React.useRef(null);
|
|
3494
|
+
const attachInputRef = React.useRef(null);
|
|
3495
|
+
const [dragOver, setDragOver] = React.useState(false);
|
|
3496
|
+
|
|
3497
|
+
const canAttachImages = typeof controller.transport?.supportsPromptImageAttachments === "function"
|
|
3498
|
+
&& controller.transport.supportsPromptImageAttachments()
|
|
3499
|
+
&& typeof controller.transport?.uploadArtifactFromFile === "function";
|
|
3500
|
+
const imageAttachments = canAttachImages
|
|
3501
|
+
? promptState.promptAttachments.filter((a) => a?.kind === "image")
|
|
3502
|
+
: EMPTY_ARRAY;
|
|
3503
|
+
|
|
3504
|
+
// Object URLs for chip thumbnails — created per staged File, revoked when
|
|
3505
|
+
// the chip leaves (send/remove) or the composer unmounts.
|
|
3506
|
+
const thumbUrlsRef = React.useRef(new Map());
|
|
3507
|
+
React.useEffect(() => {
|
|
3508
|
+
const urls = thumbUrlsRef.current;
|
|
3509
|
+
const liveFiles = new Set(imageAttachments.map((a) => a.file));
|
|
3510
|
+
for (const [file, url] of urls) {
|
|
3511
|
+
if (!liveFiles.has(file)) {
|
|
3512
|
+
URL.revokeObjectURL(url);
|
|
3513
|
+
urls.delete(file);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
for (const attachment of imageAttachments) {
|
|
3517
|
+
if (attachment.file && !urls.has(attachment.file)) {
|
|
3518
|
+
try {
|
|
3519
|
+
urls.set(attachment.file, URL.createObjectURL(attachment.file));
|
|
3520
|
+
} catch { /* thumbnail is a nicety — the chip still renders */ }
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}, [imageAttachments]);
|
|
3524
|
+
React.useEffect(() => () => {
|
|
3525
|
+
for (const url of thumbUrlsRef.current.values()) URL.revokeObjectURL(url);
|
|
3526
|
+
thumbUrlsRef.current.clear();
|
|
3527
|
+
}, []);
|
|
3528
|
+
|
|
3529
|
+
const stageImageFiles = React.useCallback((files) => {
|
|
3530
|
+
if (!canAttachImages) return 0;
|
|
3531
|
+
const list = Array.from(files || []).filter((f) => f && typeof f.type === "string" && f.type.startsWith("image/"));
|
|
3532
|
+
if (list.length === 0) return 0;
|
|
3533
|
+
const result = controller.addPendingImageFiles(list);
|
|
3534
|
+
return result?.accepted || 0;
|
|
3535
|
+
}, [controller, canAttachImages]);
|
|
3322
3536
|
|
|
3323
3537
|
React.useEffect(() => {
|
|
3324
3538
|
const inputNode = inputRef.current;
|
|
@@ -3373,8 +3587,52 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null
|
|
|
3373
3587
|
const selectedReadOnly = selectedQueued || selectedCancelling;
|
|
3374
3588
|
|
|
3375
3589
|
return React.createElement("div", {
|
|
3376
|
-
className: `ps-prompt-shell${mobile ? " is-mobile" : ""}`,
|
|
3590
|
+
className: `ps-prompt-shell${mobile ? " is-mobile" : ""}${dragOver ? " is-drag-over" : ""}`,
|
|
3591
|
+
...(canAttachImages ? {
|
|
3592
|
+
onDragOver: (event) => {
|
|
3593
|
+
if (event.dataTransfer?.types?.includes?.("Files")) {
|
|
3594
|
+
event.preventDefault();
|
|
3595
|
+
setDragOver(true);
|
|
3596
|
+
}
|
|
3597
|
+
},
|
|
3598
|
+
onDragLeave: (event) => {
|
|
3599
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragOver(false);
|
|
3600
|
+
},
|
|
3601
|
+
onDrop: (event) => {
|
|
3602
|
+
setDragOver(false);
|
|
3603
|
+
if (event.dataTransfer?.files?.length) {
|
|
3604
|
+
event.preventDefault();
|
|
3605
|
+
stageImageFiles(event.dataTransfer.files);
|
|
3606
|
+
}
|
|
3607
|
+
},
|
|
3608
|
+
} : {}),
|
|
3377
3609
|
},
|
|
3610
|
+
imageAttachments.length > 0
|
|
3611
|
+
? React.createElement("div", { className: "ps-prompt-attachments" },
|
|
3612
|
+
imageAttachments.map((attachment, index) => {
|
|
3613
|
+
const thumbUrl = attachment.file ? thumbUrlsRef.current.get(attachment.file) : null;
|
|
3614
|
+
// No filename in the chip: pasted images all arrive as the
|
|
3615
|
+
// browser's generic "image.png", and the name is replaced
|
|
3616
|
+
// by a deterministic artifact name at send anyway. The
|
|
3617
|
+
// thumbnail IS the identity; the name survives as a tooltip.
|
|
3618
|
+
return React.createElement("div", {
|
|
3619
|
+
key: `attach:${index}:${attachment.filename}`,
|
|
3620
|
+
className: "ps-attach-chip",
|
|
3621
|
+
title: `${attachment.filename} · ${formatAttachmentSize(attachment.sizeBytes)}`,
|
|
3622
|
+
},
|
|
3623
|
+
thumbUrl
|
|
3624
|
+
? React.createElement("img", { className: "ps-attach-thumb", src: thumbUrl, alt: attachment.filename })
|
|
3625
|
+
: React.createElement("div", { className: "ps-attach-thumb is-placeholder" }, "🖼"),
|
|
3626
|
+
React.createElement("div", { className: "ps-attach-size" }, formatAttachmentSize(attachment.sizeBytes)),
|
|
3627
|
+
React.createElement("button", {
|
|
3628
|
+
type: "button",
|
|
3629
|
+
className: "ps-attach-remove",
|
|
3630
|
+
title: `Remove ${attachment.filename}`,
|
|
3631
|
+
"aria-label": `Remove attachment ${attachment.filename}`,
|
|
3632
|
+
onClick: () => controller.removePendingImageAttachment(index),
|
|
3633
|
+
}, "✕"));
|
|
3634
|
+
}))
|
|
3635
|
+
: null,
|
|
3378
3636
|
React.createElement("label", { className: "ps-prompt-label" }, promptState.answerMode ? "answer" : selectedCancelling ? "cancelling" : selectedQueued ? "queued" : promptState.editingPending ? "pending" : "you"),
|
|
3379
3637
|
React.createElement("textarea", {
|
|
3380
3638
|
ref: inputRef,
|
|
@@ -3397,6 +3655,21 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null
|
|
|
3397
3655
|
onFocus: () => controller.setFocus("prompt"),
|
|
3398
3656
|
onSelect: (event) => controller.setPromptCursor(event.currentTarget.selectionStart || 0),
|
|
3399
3657
|
onChange: (event) => controller.setPrompt(event.currentTarget.value, event.currentTarget.selectionStart || event.currentTarget.value.length),
|
|
3658
|
+
// Ctrl/Cmd+V of a copied image (or iOS/Android long-press → Paste):
|
|
3659
|
+
// clipboardData.items carries the image file(s). preventDefault only
|
|
3660
|
+
// when we actually staged one, so text pastes are untouched.
|
|
3661
|
+
onPaste: canAttachImages
|
|
3662
|
+
? (event) => {
|
|
3663
|
+
const items = Array.from(event.clipboardData?.items || []);
|
|
3664
|
+
const files = items
|
|
3665
|
+
.filter((item) => item.kind === "file" && item.type.startsWith("image/"))
|
|
3666
|
+
.map((item) => item.getAsFile())
|
|
3667
|
+
.filter(Boolean);
|
|
3668
|
+
if (files.length > 0 && stageImageFiles(files) > 0) {
|
|
3669
|
+
event.preventDefault();
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
: undefined,
|
|
3400
3673
|
onKeyDown: (event) => {
|
|
3401
3674
|
if (event.key === "Tab" && !event.shiftKey && controller.acceptPromptReferenceAutocomplete()) {
|
|
3402
3675
|
event.preventDefault();
|
|
@@ -3428,6 +3701,32 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null
|
|
|
3428
3701
|
},
|
|
3429
3702
|
}),
|
|
3430
3703
|
React.createElement("div", { className: "ps-prompt-actions" },
|
|
3704
|
+
canAttachImages
|
|
3705
|
+
? React.createElement(React.Fragment, null,
|
|
3706
|
+
React.createElement("input", {
|
|
3707
|
+
ref: attachInputRef,
|
|
3708
|
+
type: "file",
|
|
3709
|
+
// image/* (not the exact MIME list) so phones offer the
|
|
3710
|
+
// photo library and camera; exact types are validated
|
|
3711
|
+
// on staging and again at the API edge.
|
|
3712
|
+
accept: "image/*",
|
|
3713
|
+
multiple: true,
|
|
3714
|
+
className: "ps-hidden-file-input",
|
|
3715
|
+
"aria-hidden": true,
|
|
3716
|
+
tabIndex: -1,
|
|
3717
|
+
onChange: (event) => {
|
|
3718
|
+
stageImageFiles(event.currentTarget.files);
|
|
3719
|
+
event.currentTarget.value = "";
|
|
3720
|
+
},
|
|
3721
|
+
}),
|
|
3722
|
+
React.createElement("button", {
|
|
3723
|
+
type: "button",
|
|
3724
|
+
className: "ps-mini-button ps-attach-button",
|
|
3725
|
+
title: "Attach images (or paste / drop them into the composer)",
|
|
3726
|
+
"aria-label": "Attach images",
|
|
3727
|
+
onClick: () => attachInputRef.current?.click(),
|
|
3728
|
+
}, "📎"))
|
|
3729
|
+
: null,
|
|
3431
3730
|
promptState.editingPending && !selectedCancelling
|
|
3432
3731
|
? React.createElement("button", {
|
|
3433
3732
|
type: "button",
|
package/web/api/router.js
CHANGED
|
@@ -18,6 +18,13 @@ import { getPublicAuthContext } from "../auth/authz/engine.js";
|
|
|
18
18
|
|
|
19
19
|
const ERROR_STATUS_BY_CODE = {
|
|
20
20
|
INVALID_REQUEST: 400,
|
|
21
|
+
// Artifact upload validation: declared content type contradicts the
|
|
22
|
+
// sniffed bytes, or the body exceeds the artifact size caps.
|
|
23
|
+
ARTIFACT_CONTENT_TYPE_MISMATCH: 400,
|
|
24
|
+
ARTIFACT_TOO_LARGE: 413,
|
|
25
|
+
// Image attachment refs on sendMessage failed validation (missing
|
|
26
|
+
// artifact, non-image type, size/count caps).
|
|
27
|
+
INVALID_ATTACHMENT: 400,
|
|
21
28
|
// GitHub Copilot model requested without a worker or per-user key.
|
|
22
29
|
GHCP_KEY_MISSING: 400,
|
|
23
30
|
PORTAL_AUTH_REQUIRED: 401,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--ps-page-background: #0d1117;--ps-page-foreground: #f0f6fc;--ps-surface: #0d1117;--ps-background: #0d1117;--ps-foreground: #f0f6fc;--ps-muted: #8b949e;--ps-border: #6e7681;--ps-selection-background: #58a6ff;--ps-selection-foreground: #0d1117;--ps-highlight-background: #1f6feb;--ps-highlight-foreground: #ffffff;--ps-modal-backdrop: rgba(13, 17, 23, .72);--ps-modal-background: #161b22;--ps-modal-border: #30363d;--ps-modal-foreground: #f0f6fc;--ps-modal-muted: #8b949e;--ps-modal-selected-background: rgba(31, 111, 235, .16);--ps-modal-selected-border: #58a6ff;--ps-modal-selected-foreground: #ffffff;--ps-font-size-base: 13px;--ps-font-size-dense: 12px;--ps-line-height-base: 1.28;--ps-line-height-dense: 1.18;--ps-radius-sm: 6px;--ps-radius-md: 8px;--ps-scrollbar-size: 10px;--ps-scrollbar-track: rgba(110, 118, 129, .14);--ps-scrollbar-thumb: color-mix(in srgb, var(--ps-border) 56%, var(--ps-surface));--ps-scrollbar-thumb-hover: color-mix(in srgb, var(--ps-modal-selected-border) 42%, var(--ps-border))}*{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:var(--ps-scrollbar-thumb) var(--ps-scrollbar-track)}html,body,#root{margin:0;min-height:100%;height:100%;background:var(--ps-page-background);color:var(--ps-page-foreground);font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:var(--ps-font-size-base);line-height:var(--ps-line-height-base)}body{overflow:hidden;-webkit-text-size-adjust:100%}button,input,textarea{font:inherit}button{cursor:pointer}.portal-app-shell,.portal-gate{min-height:var(--ps-app-height, 100%);height:var(--ps-app-height, 100%);display:flex;flex-direction:column;background:radial-gradient(circle at top left,color-mix(in srgb,var(--ps-highlight-background) 18%,transparent),transparent 42%),radial-gradient(circle at top right,color-mix(in srgb,var(--ps-modal-selected-border) 16%,transparent),transparent 38%),var(--ps-page-background)}.portal-gate{align-items:center;justify-content:center;padding:24px}.portal-gate-card{width:min(480px,100%);border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);background:color-mix(in srgb,var(--ps-modal-background) 92%,transparent);box-shadow:0 28px 80px #00000052;padding:28px;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.portal-gate-brand{display:flex;flex-direction:column;align-items:flex-start;gap:14px;margin-bottom:18px}.portal-gate-kicker,.portal-header-kicker{color:var(--ps-muted);font-size:12px;letter-spacing:.08em;text-transform:uppercase}.portal-gate-title{margin:10px 0 14px;font-size:clamp(28px,5vw,42px);line-height:1.05}.portal-gate-copy{margin:0;color:var(--ps-muted);line-height:1.6}.portal-primary-button,.portal-secondary-button,.ps-toolbar-button,.ps-mini-button,.ps-send-button,.ps-modal-button,.ps-mobile-nav-button,.ps-tab,.ps-filter-option,.ps-list-button,.ps-modal-close{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);color:var(--ps-foreground);transition:background .12s ease,border-color .12s ease,transform .12s ease}.portal-primary-button,.ps-send-button,.ps-modal-button.is-primary{background:var(--ps-highlight-background);border-color:var(--ps-highlight-background);color:var(--ps-highlight-foreground)}.ps-modal-button.is-danger{background:#dc2626;border-color:#dc2626;color:#fff}.ps-modal-button.is-danger:hover{background:#ef4444;border-color:#ef4444}.portal-primary-button,.portal-secondary-button{padding:10px 14px;margin-top:20px}.portal-secondary-button{margin-top:0;flex:0 0 auto}.portal-header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 50%,transparent);background:color-mix(in srgb,var(--ps-page-background) 86%,transparent);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}.portal-header-brand{display:flex;align-items:center;gap:12px;min-width:0}.portal-header-brand-copy{display:flex;flex-direction:column;gap:3px;min-width:0}.portal-logo-frame{width:38px;height:38px;border-radius:var(--ps-radius-sm);border:1px solid color-mix(in srgb,var(--ps-modal-selected-border) 52%,var(--ps-modal-border));background:radial-gradient(circle at 28% 24%,color-mix(in srgb,var(--ps-modal-selected-border) 20%,transparent),transparent 58%),linear-gradient(135deg,color-mix(in srgb,var(--ps-highlight-background) 20%,var(--ps-surface)),color-mix(in srgb,var(--ps-background) 92%,transparent));box-shadow:inset 0 1px color-mix(in srgb,white 10%,transparent),0 10px 24px #0000002e;display:grid;place-items:center;flex:0 0 auto;overflow:hidden}.portal-logo-frame.is-large{width:76px;height:76px;border-radius:16px}.portal-logo-frame.has-image{background:radial-gradient(circle at 28% 24%,color-mix(in srgb,white 10%,transparent),transparent 58%),linear-gradient(135deg,color-mix(in srgb,var(--ps-surface) 94%,white 6%),color-mix(in srgb,var(--ps-background) 90%,transparent))}.portal-logo{width:24px;height:24px}.portal-logo-frame.is-large .portal-logo{width:44px;height:44px}.portal-logo-image{width:74%;height:74%;object-fit:contain;display:block}.portal-logo-frame.is-large .portal-logo-image{width:82%;height:82%}.portal-logo-ring,.portal-logo-link{stroke:color-mix(in srgb,var(--ps-modal-selected-border) 82%,var(--ps-page-foreground));stroke-linecap:round;stroke-linejoin:round}.portal-logo-ring{stroke-width:3;opacity:.46}.portal-logo-link{stroke-width:2.5;opacity:.9}.portal-logo-core{fill:var(--ps-highlight-background)}.portal-logo-node{stroke:color-mix(in srgb,var(--ps-page-background) 70%,transparent);stroke-width:1.5}.portal-logo-node-a,.portal-logo-node-d{fill:#7ee787}.portal-logo-node-b,.portal-logo-node-e{fill:#58a6ff}.portal-logo-node-c,.portal-logo-node-f{fill:#d2a8ff}.portal-header-user{display:inline-flex;align-items:flex-start;gap:10px;align-self:start}.portal-header-meta{display:inline-flex;flex-direction:column;align-items:flex-end;gap:4px;min-width:0}.portal-header-version{display:inline-flex;align-items:center;padding:3px 8px;border:1px solid color-mix(in srgb,var(--ps-border) 48%,transparent);border-radius:999px;background:color-mix(in srgb,var(--ps-surface) 90%,transparent);color:var(--ps-muted);font-size:.72rem;line-height:1;white-space:nowrap}.portal-header-status{max-width:min(44rem,42vw);color:var(--ps-muted);font-size:.72rem;line-height:1;text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.portal-mobile-status{display:none}.portal-mobile-status-text{flex:1;min-width:0;color:var(--ps-foreground);font-size:.9rem;line-height:1.2;overflow-wrap:anywhere}.portal-mobile-status-dismiss{flex:none;appearance:none;border:none;background:transparent;color:var(--ps-muted);font-size:.9rem;line-height:1;padding:4px 6px;cursor:pointer;border-radius:6px}.portal-mobile-status-dismiss:hover{color:var(--ps-foreground);background:color-mix(in srgb,var(--ps-surface) 80%,transparent)}.portal-header-identity,.portal-header-identity-stack{min-width:0}.portal-header-identity-stack{display:flex;flex-direction:column;align-items:flex-start;gap:1px;text-align:left}.portal-header-name,.portal-header-email,.portal-header-identity{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.portal-header-name{color:var(--ps-foreground);font-size:.9rem;font-weight:700;line-height:1.05}.portal-header-email{color:var(--ps-muted);font-size:.78rem;line-height:1.05}.portal-header-identity{color:var(--ps-foreground);font-size:.84rem}.portal-header-identity.is-muted{color:var(--ps-muted)}.portal-main{flex:1;min-height:0;padding:10px;overflow:hidden}.ps-web-shell{min-height:0;height:100%;display:flex;flex-direction:column;gap:8px;overflow:hidden}.ps-toolbar{display:flex;align-items:center;justify-content:space-between;gap:6px}.ps-toolbar.is-mobile{flex-direction:column;align-items:stretch;justify-content:flex-start}.ps-toolbar-row{display:flex;align-items:center;gap:6px;min-width:0}.ps-toolbar-row-actions{display:flex;align-items:center;gap:6px;min-width:0;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none}.ps-toolbar-row-actions::-webkit-scrollbar{display:none}.ps-toolbar-row-primary,.ps-toolbar-row-secondary{min-width:0}.ps-toolbar-row-secondary{justify-content:space-between}.ps-toolbar-actions{display:flex;flex-wrap:wrap;gap:6px;min-width:0}.ps-toolbar-status{flex:0 1 auto;color:var(--ps-muted);font-size:10px;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ps-toolbar-button,.ps-mini-button,.ps-modal-button,.ps-mobile-nav-button,.ps-tab,.ps-filter-option,.ps-modal-close{padding:6px 10px;min-height:34px;font-size:.92rem;line-height:1}.ps-workspace{flex:1;min-height:0;overflow:hidden}.ps-workspace>*{min-height:0}.ps-workspace-full{min-height:0;height:100%;display:flex}.ps-workspace-full>.ps-panel{flex:1;min-height:0}.ps-workspace-grid{display:grid;grid-template-rows:minmax(0,1fr);gap:8px;min-width:0;width:100%;max-width:100%;min-height:0;height:100%;overflow:hidden}.ps-workspace-column{display:grid;gap:8px;min-width:0;width:100%;max-width:100%;min-height:0;overflow:hidden}.ps-workspace-main-grid{display:grid;grid-template-rows:minmax(0,1fr);gap:8px;min-width:0;width:100%;max-width:100%;min-height:0;height:100%;overflow:hidden}.ps-workspace-pane-slot{min-width:0;width:100%;max-width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.ps-workspace-pane-slot>.ps-panel{flex:1;min-width:0;width:100%;max-width:100%;min-height:0}.ps-chat-focus-shell{min-height:0;min-width:0;width:100%;max-width:100%;height:100%;display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px;overflow:hidden}.ps-chat-focus-rail{display:flex;align-items:center;gap:6px;padding:4px 6px;border:1px solid color-mix(in srgb,var(--ps-border) 42%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent)}.ps-chat-focus-button{flex:0 0 auto}.ps-chat-focus-status{margin-left:auto;color:var(--ps-muted);font-size:.72rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ps-chat-focus-body{position:relative;min-height:0;min-width:0;display:flex;overflow:hidden}.ps-chat-focus-body>.ps-panel{flex:1;min-height:0;min-width:0}.ps-chat-focus-body .ps-panel-body,.ps-chat-focus-body .ps-scroll-panel{min-width:0}.ps-chat-focus-body .ps-line,.ps-chat-focus-body .ps-chat-card-line{overflow-wrap:anywhere}.ps-chat-focus-overlay{position:absolute;top:10px;bottom:10px;width:min(34rem,calc(100% - 20px));max-width:min(34rem,42%);min-width:0;z-index:6;pointer-events:none;overflow:hidden}.ps-chat-focus-overlay.is-left{left:10px}.ps-chat-focus-overlay.is-right{right:10px}.ps-chat-focus-overlay>.ps-panel{height:100%;min-width:0;max-width:100%;pointer-events:auto;background:linear-gradient(color-mix(in srgb,var(--ps-surface) 96%,transparent),color-mix(in srgb,var(--ps-surface) 96%,transparent)),var(--ps-page-background);box-shadow:0 20px 48px #00000057,inset 0 0 0 1px color-mix(in srgb,var(--ps-page-background) 35%,transparent)}.ps-mobile-workspace{min-height:0;min-width:0;width:100%;max-width:100%;height:100%;display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px;overflow:hidden}.ps-mobile-session-pane{max-height:min(14vh,96px)}.ps-mobile-session-pane .ps-action-list{min-height:0}.ps-mobile-session-pane .ps-action-list .ps-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal;word-break:normal}.ps-mobile-session-collapsed .ps-panel-body{flex:0 0 auto}.ps-mobile-session-summary{padding:0 10px 10px;color:var(--ps-muted);font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-mobile-chat-pane{min-height:0;min-width:0;width:100%;max-width:100%;display:flex;flex-direction:column;overflow:hidden}.ps-mobile-chat-pane>.ps-panel{flex:1;min-width:0;width:100%;max-width:100%;min-height:0}.ps-mobile-pane-fill{min-height:0;height:100%;display:flex;flex-direction:column;overflow:hidden}.ps-mobile-pane-fill>.ps-panel{flex:1;min-height:0}.ps-column-resizer{align-self:stretch;width:16px;min-height:0;padding:0;border:0;border-radius:999px;background:transparent;display:grid;place-items:center;cursor:col-resize;position:relative}.ps-row-resizer{justify-self:stretch;height:16px;min-width:0;padding:0;border:0;border-radius:999px;background:transparent;display:grid;place-items:center;cursor:row-resize;position:relative}.ps-column-resizer:before{content:"";position:absolute;top:10px;bottom:10px;left:50%;width:1px;transform:translate(-50%);background:color-mix(in srgb,var(--ps-border) 52%,transparent)}.ps-row-resizer:before{content:"";position:absolute;left:10px;right:10px;top:50%;height:1px;transform:translateY(-50%);background:color-mix(in srgb,var(--ps-border) 52%,transparent)}.ps-column-resizer-handle{position:relative;z-index:1;display:grid;gap:4px;padding:10px 4px;border-radius:999px;border:1px solid color-mix(in srgb,var(--ps-modal-border) 78%,transparent);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);box-shadow:0 6px 20px #0000002e}.ps-row-resizer-handle{position:relative;z-index:1;display:flex;gap:4px;padding:4px 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--ps-modal-border) 78%,transparent);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);box-shadow:0 6px 20px #0000002e}.ps-column-resizer-dot,.ps-row-resizer-dot{width:3px;height:3px;border-radius:999px;background:color-mix(in srgb,var(--ps-modal-selected-border) 84%,var(--ps-page-foreground))}.ps-column-resizer:hover .ps-column-resizer-handle,.ps-column-resizer.is-dragging .ps-column-resizer-handle,.ps-column-resizer:focus-visible .ps-column-resizer-handle{border-color:color-mix(in srgb,var(--ps-modal-selected-border) 72%,var(--ps-modal-border));background:color-mix(in srgb,var(--ps-modal-selected-background) 72%,var(--ps-surface))}.ps-row-resizer:hover .ps-row-resizer-handle,.ps-row-resizer.is-dragging .ps-row-resizer-handle,.ps-row-resizer:focus-visible .ps-row-resizer-handle{border-color:color-mix(in srgb,var(--ps-modal-selected-border) 72%,var(--ps-modal-border));background:color-mix(in srgb,var(--ps-modal-selected-background) 72%,var(--ps-surface))}.ps-column-resizer:focus-visible{outline:none}.ps-row-resizer:focus-visible{outline:none}body.is-resizing-pane-x,body.is-resizing-pane-x *{cursor:col-resize!important;-webkit-user-select:none!important;user-select:none!important}body.is-resizing-pane-y,body.is-resizing-pane-y *{cursor:row-resize!important;-webkit-user-select:none!important;user-select:none!important}.ps-panel{position:relative;min-height:0;display:flex;flex-direction:column;overflow:hidden;border:2px solid var(--ps-panel-accent, var(--ps-border));border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 96%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-page-background) 35%,transparent)}.ps-panel.is-focused{box-shadow:0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 40%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--ps-page-background) 35%,transparent)}.ps-panel-header{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:30px;padding:4px 10px;border-bottom:1px solid color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 28%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 16%,var(--ps-surface)),color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 10%,var(--ps-surface)));margin-bottom:0}.ps-panel-title{display:inline-flex;align-items:center;gap:4px;padding:0;background:transparent;font-size:.94rem;font-weight:700;line-height:1.02}.ps-panel-header-right{display:inline-flex;align-items:center;gap:8px;min-width:0;margin-left:auto}.ps-panel-title-right{display:inline-flex;align-items:center;gap:0;min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.82rem;line-height:1.35}.ps-panel-actions{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;padding-left:0;background:transparent}.ps-panel-body,.ps-scroll-panel{flex:1;min-width:0;width:100%;max-width:100%;min-height:0}.ps-panel-body{display:flex;flex-direction:column;min-height:0;padding-top:2px}.ps-scroll-panel{overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x pan-y;scrollbar-gutter:stable;padding:6px 10px 10px;font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-panel-sticky{flex:0 0 auto;padding:6px 10px 0;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 35%,transparent);font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-panel-sticky.is-scroll-sync{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x;scrollbar-gutter:stable both-edges}.ps-panel-bottom-sticky{flex:0 0 auto;max-height:34%;overflow:auto;padding:4px 10px 8px;border-top:1px solid color-mix(in srgb,var(--ps-border) 45%,transparent);background:color-mix(in srgb,var(--ps-surface) 94%,#0f172a 6%);font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense);scrollbar-gutter:stable both-edges}.ps-line{min-height:1.16rem;white-space:pre-wrap;line-height:1.16rem;word-break:break-word}.ps-panel.has-preserved-sticky .ps-panel-sticky .ps-line,.ps-scroll-panel.is-preserve .ps-line{white-space:pre;word-break:normal;overflow-wrap:normal}.ps-scroll-panel.is-preserve .ps-line{min-width:max-content}.ps-chat-preserve-block{max-width:100%;overflow-x:auto}.ps-scroll-panel.is-wrapped .ps-line.ps-line-preserve,.ps-line-preserve{white-space:pre;word-break:normal;overflow-wrap:normal;min-width:max-content}.ps-chat-preserve-block.is-splash-mobile{display:none}@media(max-width:920px){.ps-chat-preserve-block.is-splash-desktop{display:none}.ps-chat-preserve-block.is-splash-mobile{display:block}.portal-header-status{display:none}.portal-mobile-status{display:flex;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--ps-border);background:var(--ps-surface)}}@media(max-width:920px){.ps-chat-preserve-block{overflow-x:hidden}.ps-chat-preserve-block .ps-line-preserve,.ps-scroll-panel.is-wrapped .ps-line.ps-line-preserve{white-space:pre-wrap;overflow-wrap:anywhere;word-break:normal;min-width:0}}.ps-sequence-turn-divider{width:100%;margin:4px 0;padding:2px 6px;border:0;--ps-sequence-divider: #4f6f96;border-top:1px solid color-mix(in srgb,var(--ps-sequence-divider) 82%,transparent);border-bottom:1px solid color-mix(in srgb,var(--ps-sequence-divider) 42%,transparent);border-radius:0;background:transparent;color:color-mix(in srgb,var(--ps-sequence-divider) 78%,var(--ps-text-muted));font:inherit;text-align:center;cursor:pointer}.ps-sequence-turn-divider:hover,.ps-sequence-turn-divider:focus-visible{color:var(--ps-text);background:color-mix(in srgb,var(--ps-surface-raised) 72%,transparent);outline:none}.ps-sequence-turn-caret{color:var(--ps-sequence-divider)}.ps-sequence-turn-rule{color:color-mix(in srgb,var(--ps-sequence-divider) 76%,var(--ps-text-muted))}.ps-sequence-turn-key{color:var(--ps-text-muted)}.ps-sequence-turn-model{color:var(--ps-text)}.ps-sequence-turn-tokens{color:var(--ps-success)}.ps-sequence-turn-duration{color:var(--ps-warning)}.ps-sequence-turn-details{margin:0 12px 8px;padding:8px 10px;border-left:2px solid var(--ps-accent);color:var(--ps-text-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.5}.ps-sequence-turn-detail-label{color:var(--ps-accent)}.ps-scroll-panel.is-wrapped .ps-line,.ps-modal-details .ps-line,.ps-filter-column .ps-line{white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;max-width:100%}.ps-chat-card{margin:8px 0;border:1px solid color-mix(in srgb,var(--ps-chat-card-accent, var(--ps-border)) 78%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 98%,transparent);overflow:hidden;width:auto;max-width:100%}.ps-chat-card-header{padding:8px 12px 6px;border-bottom:1px solid color-mix(in srgb,var(--ps-chat-card-accent, var(--ps-border)) 28%,transparent);background:color-mix(in srgb,var(--ps-chat-card-accent, var(--ps-surface)) 10%,transparent)}.ps-chat-card-body{padding:8px 12px 10px}.ps-chat-card-line{white-space:pre-wrap;word-break:break-word}.ps-chat-card-line+.ps-chat-card-line{margin-top:6px}.ps-chat-panel .ps-line,.ps-chat-panel .ps-chat-card-line{min-height:1.24rem;line-height:1.24rem}.ps-system-notice{margin:10px 0 12px;color:var(--ps-muted)}.ps-system-notice[open]{margin-bottom:14px}.ps-system-notice-summary{cursor:pointer;list-style:none;-webkit-user-select:none;user-select:none;opacity:.86;transition:opacity .12s ease}.ps-system-notice-summary:hover,.ps-system-notice[open] .ps-system-notice-summary{opacity:1}.ps-system-notice-summary::-webkit-details-marker{display:none}.ps-system-notice-summary-text{display:inline-block;white-space:pre-wrap}.ps-system-notice-summary:before{content:"▸ ";color:color-mix(in srgb,var(--ps-muted) 84%,transparent)}.ps-system-notice[open] .ps-system-notice-summary:before{content:"▾ "}.ps-system-notice-body{margin-top:8px;padding-left:12px;border-left:1px solid color-mix(in srgb,var(--ps-border) 42%,transparent);color:var(--ps-muted)}.ps-system-notice-body .ps-markdown-preview{gap:10px}.ps-system-notice-body .ps-md-paragraph,.ps-system-notice-body .ps-md-list,.ps-system-notice-body .ps-md-quote{color:var(--ps-muted)}.ps-chat-table-wrap{margin:8px auto 8px 0;border:1px solid color-mix(in srgb,var(--ps-border) 55%,transparent);border-radius:var(--ps-radius-sm);overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;width:fit-content;max-width:100%;scrollbar-width:thin;scrollbar-color:var(--ps-scrollbar-thumb) transparent}.ps-chat-table-wrap::-webkit-scrollbar,.ps-md-table-wrap::-webkit-scrollbar,.ps-md-code-pre::-webkit-scrollbar{width:var(--ps-scrollbar-size);height:var(--ps-scrollbar-size)}.ps-chat-table-wrap::-webkit-scrollbar-track,.ps-md-table-wrap::-webkit-scrollbar-track,.ps-md-code-pre::-webkit-scrollbar-track{background:transparent}.ps-chat-table-wrap::-webkit-scrollbar-thumb,.ps-md-table-wrap::-webkit-scrollbar-thumb,.ps-md-code-pre::-webkit-scrollbar-thumb{background:var(--ps-scrollbar-thumb);border:2px solid transparent;border-radius:999px;background-clip:padding-box;min-height:28px;min-width:28px}.ps-chat-table-wrap::-webkit-scrollbar-thumb:hover,.ps-md-table-wrap::-webkit-scrollbar-thumb:hover,.ps-md-code-pre::-webkit-scrollbar-thumb:hover{background:var(--ps-scrollbar-thumb-hover);border:2px solid transparent;background-clip:padding-box}.ps-chat-table-wrap.is-fit-width,.ps-md-table-wrap.is-fit-width{width:fit-content;max-width:100%}.ps-chat-table{width:max-content;border-collapse:collapse;table-layout:auto}.ps-chat-table.is-fit-width,.ps-md-table.is-fit-width{width:fit-content;max-width:100%;min-width:var(--ps-table-min-width, 0px);table-layout:auto}.ps-chat-table th,.ps-chat-table td{padding:6px 8px;border-right:1px solid color-mix(in srgb,var(--ps-border) 34%,transparent);border-bottom:1px solid color-mix(in srgb,var(--ps-border) 34%,transparent);white-space:pre-wrap;word-break:break-word;vertical-align:top}.ps-chat-table.is-fit-width th,.ps-chat-table.is-fit-width td,.ps-md-table.is-fit-width th,.ps-md-table.is-fit-width td{min-width:0;white-space:normal;word-break:break-word;overflow-wrap:anywhere}.ps-chat-table-wrap.is-fit-width.has-flex-column,.ps-md-table-wrap.is-fit-width.has-flex-column{width:100%;max-width:100%}.ps-chat-table.is-fit-width.has-flex-column,.ps-md-table.is-fit-width.has-flex-column{width:max(100%,var(--ps-table-min-width, 0px));min-width:var(--ps-table-min-width, 0px);table-layout:fixed}.ps-chat-table.is-fit-width.has-flex-column th,.ps-chat-table.is-fit-width.has-flex-column td,.ps-md-table.is-fit-width.has-flex-column th,.ps-md-table.is-fit-width.has-flex-column td{overflow-wrap:normal;word-break:normal}.ps-chat-table.is-fit-width.has-flex-column th.is-flex-column,.ps-chat-table.is-fit-width.has-flex-column td.is-flex-column,.ps-md-table.is-fit-width.has-flex-column th.is-flex-column,.ps-md-table.is-fit-width.has-flex-column td.is-flex-column{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.ps-chat-table th:last-child,.ps-chat-table td:last-child{border-right:0}.ps-chat-table tbody tr:last-child td{border-bottom:0}.ps-chat-table th{text-align:left;font-weight:700;color:var(--ps-foreground);background:color-mix(in srgb,var(--ps-highlight-background) 10%,var(--ps-surface))}.ps-table-cell-value{min-width:0}.ps-chat-code-block{margin:8px 0;width:auto;max-width:100%}.ps-action-list{display:flex;flex-direction:column;flex:1 1 auto;gap:4px;padding:6px 10px 10px;min-width:0;min-height:0;width:100%;max-width:100%;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y;scrollbar-gutter:stable both-edges;font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-session-pane.ps-chat-focus-pane .ps-action-list.ps-session-list{overflow-x:auto;touch-action:pan-x pan-y;padding-bottom:calc(6px + var(--ps-scrollbar-size))}.ps-session-pane.ps-chat-focus-pane .ps-session-list-button{width:max-content;min-width:100%;max-width:none;overflow:visible}.ps-session-pane.ps-chat-focus-pane .ps-session-row-content{display:inline-block;min-width:max-content;overflow:visible;max-width:none}.ps-session-pane.ps-chat-focus-pane .ps-action-list .ps-line{max-width:none;min-width:0;white-space:nowrap;overflow:visible;text-overflow:clip}.ps-list-button{width:100%;min-width:0;max-width:100%;overflow:hidden;text-align:left;padding:7px 9px;flex:0 0 auto}.ps-action-list .ps-line{display:block;min-width:0;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ps-session-pane .ps-panel-body{padding:2px 8px 8px}.ps-session-pane .ps-session-list{gap:1px;padding:2px 0 6px}.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-list{overflow-x:auto;overflow-y:auto;touch-action:pan-x pan-y;padding-bottom:calc(6px + var(--ps-scrollbar-size))}.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-list-button{width:max-content;min-width:100%;max-width:none;overflow:visible}.ps-session-pane:not(.ps-mobile-session-pane):not(.ps-chat-focus-pane) .ps-session-list-button+.ps-session-list-button{border-top:1px solid color-mix(in srgb,var(--ps-border) 22%,transparent)}.ps-session-pane .ps-session-list-button{display:block;border:0;border-radius:2px;background:transparent;box-shadow:none;color:inherit;padding:3px 8px;min-height:1.55rem;white-space:normal;flex:0 0 auto}.ps-session-pane .ps-session-list-button:hover{transform:none;border-color:transparent;background:color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 7%,transparent)}.ps-session-pane .ps-session-list-button.is-selected{border-color:transparent;background:color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 15%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 80%,transparent),inset 3px 0 color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 90%,transparent);color:inherit}.ps-session-pane .ps-session-list-button.is-multiselected{background:color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 22%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 60%,transparent),inset 3px 0 color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 95%,transparent)}.ps-session-pane .ps-session-list-button.is-pinned{background:color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 5%,transparent)}.ps-session-pane .ps-session-list-button:focus-visible{outline:none;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 80%,transparent),inset 3px 0 color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 90%,transparent),0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 35%,transparent)}.ps-session-row-content{display:block;min-width:0;max-width:100%;overflow:visible}.ps-session-row-line{display:flex;align-items:baseline;gap:8px;min-width:0;max-width:100%}.ps-session-row-title{min-width:0;flex:1 1 auto;line-height:1.28}.ps-session-row-ctx{flex:0 0 auto;margin-left:auto;padding-inline-start:8px;font-variant-numeric:tabular-nums;white-space:nowrap;opacity:.96}.ps-session-row-detail{margin-top:3px;padding-inline-start:20px;line-height:1.24;font-size:.94em;opacity:.9}.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-row-content,.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-row-line,.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-row-title,.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-row-detail{width:max-content;min-width:max-content;max-width:none}.ps-session-pane:not(.ps-mobile-session-pane) .ps-session-row-title{white-space:nowrap;overflow:visible}.ps-session-pane .ps-action-list .ps-line{white-space:nowrap;overflow:visible;text-overflow:clip;overflow-wrap:normal;word-break:normal;max-width:none}.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-list{overflow-x:hidden;padding-bottom:6px}.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-list-button{width:100%;min-width:0;max-width:100%;overflow:hidden}.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-row-content,.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-row-line,.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-row-title,.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-row-detail{width:auto;min-width:0;max-width:100%}.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-session-row-title,.ps-session-pane.ps-mobile-session-pane .ps-session-row-title{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;overflow-wrap:anywhere;white-space:normal}.ps-workspace-main-grid.is-session-wrap .ps-session-pane .ps-action-list .ps-line{white-space:normal;overflow:hidden;overflow-wrap:anywhere;word-break:break-word;max-width:100%}.ps-session-pane.ps-mobile-session-pane .ps-action-list .ps-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal;word-break:normal}.ps-list-button.is-selected,.ps-toolbar-button.is-active,.ps-mini-button.is-active,.ps-tab.is-active,.ps-mobile-nav-button.is-active,.ps-filter-option.is-selected{border-color:var(--ps-modal-selected-border);background:var(--ps-modal-selected-background);color:var(--ps-modal-selected-foreground)}.ps-empty-state{color:var(--ps-muted);padding:6px 0}.ps-nav-error-state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:18px 12px}.ps-nav-error-message{max-width:42em;line-height:1.5}.ps-tab-row{display:flex;flex-wrap:wrap;gap:4px;padding:2px 10px 4px;align-items:center}.ps-panel .ps-mini-button,.ps-panel .ps-tab{padding:4px 8px;min-height:30px}.ps-files-grid{flex:1;min-height:0;display:grid;grid-template-rows:minmax(180px,.9fr) minmax(220px,1.3fr);gap:8px;padding:0 10px 10px}.ps-scroll-panel.is-preview,.ps-markdown-scroll{padding-top:10px}.ps-markdown-preview{display:flex;flex-direction:column;gap:14px;padding-right:4px;line-height:1.55}.ps-binary-preview-card{display:flex;flex-direction:column;gap:10px;margin:10px 10px 12px;padding:18px 18px 20px;border:1px solid color-mix(in srgb,var(--ps-border) 52%,transparent);border-radius:calc(var(--ps-radius-md) + 2px);background:radial-gradient(circle at top right,color-mix(in srgb,var(--ps-highlight-background) 18%,transparent),transparent 42%),linear-gradient(180deg,color-mix(in srgb,var(--ps-surface) 96%,transparent),color-mix(in srgb,var(--ps-background) 96%,transparent));box-shadow:inset 0 1px color-mix(in srgb,white 5%,transparent)}.ps-binary-preview-kicker{font-size:.76rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ps-muted)}.ps-binary-preview-title{margin:0;font-size:1.08rem;line-height:1.25;font-weight:800;color:var(--ps-foreground);word-break:break-word}.ps-binary-preview-meta,.ps-binary-preview-time{color:color-mix(in srgb,var(--ps-foreground) 76%,var(--ps-muted));font-size:.9rem;line-height:1.45;word-break:break-word}.ps-binary-preview-copy{margin:4px 0 0;color:var(--ps-foreground);line-height:1.6}.ps-md-heading{margin:0;font-weight:800;line-height:1.2;color:var(--ps-foreground)}.ps-md-heading.is-h1{font-size:1.45rem}.ps-md-heading.is-h2{font-size:1.28rem}.ps-md-heading.is-h3{font-size:1.12rem}.ps-md-heading.is-h4,.ps-md-heading.is-h5,.ps-md-heading.is-h6{font-size:1rem}.ps-md-paragraph,.ps-md-quote,.ps-md-list{margin:0}.ps-md-list{padding-left:22px;display:flex;flex-direction:column;gap:6px}.ps-md-list-item{line-height:1.5}.ps-md-quote{margin:0;padding:10px 14px;border-left:3px solid color-mix(in srgb,var(--ps-modal-selected-border) 78%,transparent);background:color-mix(in srgb,var(--ps-highlight-background) 8%,transparent);color:color-mix(in srgb,var(--ps-foreground) 88%,var(--ps-muted))}.ps-md-inline-code{padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--ps-highlight-background) 12%,var(--ps-surface));border:1px solid color-mix(in srgb,var(--ps-border) 35%,transparent);font-size:.94em}.ps-md-code-block{border:1px solid color-mix(in srgb,var(--ps-border) 45%,transparent);border-radius:var(--ps-radius-sm);overflow:hidden;background:color-mix(in srgb,var(--ps-background) 96%,black 4%)}.ps-md-code-header{padding:7px 12px;font-size:.82rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ps-muted);border-bottom:1px solid color-mix(in srgb,var(--ps-border) 32%,transparent);background:color-mix(in srgb,var(--ps-surface) 92%,transparent)}.ps-md-code-pre{margin:0;padding:14px 16px;overflow:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x pan-y;line-height:1.55;font-size:.95rem;white-space:pre;max-width:100%;scrollbar-width:thin;scrollbar-color:var(--ps-scrollbar-thumb) transparent}.ps-md-code-pre code{font:inherit;color:#9fe3ff}.ps-md-table-wrap{overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x pan-y;border:1px solid color-mix(in srgb,var(--ps-border) 40%,transparent);border-radius:var(--ps-radius-sm);max-width:100%;scrollbar-width:thin;scrollbar-color:var(--ps-scrollbar-thumb) transparent}.ps-md-table{width:100%;border-collapse:collapse}.ps-md-table th,.ps-md-table td{padding:9px 12px;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 25%,transparent);text-align:left;vertical-align:top;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}.ps-md-table th{background:color-mix(in srgb,var(--ps-highlight-background) 10%,var(--ps-surface));font-weight:700}.ps-md-table tbody tr:last-child td{border-bottom:0}.ps-md-link{text-decoration:underline;text-underline-offset:2px;overflow-wrap:anywhere;word-break:break-word}.ps-chat-panel .ps-panel-header{padding-bottom:8px}.ps-chat-panel .ps-panel-body{padding-top:2px}.ps-chat-panel .ps-scroll-panel{padding-top:6px}.ps-chat-panel .ps-scroll-panel.is-scrolled-down{-webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 18px,#000 100%);mask-image:linear-gradient(to bottom,transparent 0,#000 18px,#000 100%)}.ps-chat-panel .ps-scroll-panel.is-scrolled-up{-webkit-mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - 18px),transparent 100%);mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - 18px),transparent 100%)}.ps-chat-panel .ps-scroll-panel.is-scrolled-down.is-scrolled-up{-webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 18px,#000 calc(100% - 18px),transparent 100%);mask-image:linear-gradient(to bottom,transparent 0,#000 18px,#000 calc(100% - 18px),transparent 100%)}.ps-chat-panel .ps-panel-bottom-sticky{box-shadow:0 -10px 22px color-mix(in srgb,#000 24%,transparent)}.ps-status-strip{display:flex;justify-content:space-between;gap:10px;padding:0 2px;color:var(--ps-muted);font-size:9px}.ps-status-left,.ps-status-right{min-width:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ps-chat-composer{display:flex;flex-direction:column;gap:4px;padding:8px 10px 10px;border-top:1px solid color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 32%,transparent);background:color-mix(in srgb,var(--ps-surface) 98%,transparent);isolation:isolate}.ps-prompt-shell{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:8px;align-items:center;padding:0}.ps-prompt-shell.is-mobile{grid-template-columns:auto minmax(0,1fr) auto;gap:8px}.ps-prompt-actions{display:inline-flex;align-items:center;gap:8px}.ps-hidden-file-input{display:none}.ps-prompt-label{padding:0 4px 0 2px;color:var(--ps-muted);text-transform:uppercase;font-size:10px;letter-spacing:.08em;align-self:center}.ps-prompt-shell.is-mobile .ps-prompt-label{padding:0 2px 0 0}.ps-prompt-input,.ps-modal-input{width:100%;min-height:40px;resize:vertical;border:1px solid color-mix(in srgb,var(--ps-border) 60%,transparent);border-radius:var(--ps-radius-sm);background:var(--ps-background);background-clip:padding-box;color:var(--ps-foreground);caret-color:var(--ps-foreground);padding:8px 10px;outline:none;font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-prompt-input::placeholder,.ps-modal-input::placeholder{color:color-mix(in srgb,var(--ps-muted) 74%,transparent);opacity:1}.ps-prompt-shell.is-mobile .ps-prompt-input{font-size:16px;line-height:1.25}.ps-prompt-input:focus,.ps-modal-input:focus{border-color:var(--ps-modal-selected-border);box-shadow:0 0 0 3px color-mix(in srgb,var(--ps-modal-selected-border) 20%,transparent)}.ps-prompt-shell.is-drag-over .ps-prompt-input{border-color:var(--ps-modal-selected-border);background:color-mix(in srgb,var(--ps-modal-selected-background) 16%,var(--ps-background));box-shadow:0 0 0 3px color-mix(in srgb,var(--ps-modal-selected-border) 18%,transparent)}.ps-prompt-attachments{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:8px;padding:6px 0 2px}.ps-attach-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 6px;border:1px solid color-mix(in srgb,var(--ps-border) 60%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent)}.ps-attach-thumb{width:40px;height:40px;object-fit:cover;border-radius:4px;flex:0 0 auto}.ps-attach-thumb.is-placeholder{display:inline-flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--ps-border) 30%,transparent)}.ps-attach-size{color:var(--ps-muted);font-size:11px}.ps-attach-remove{flex:0 0 auto;border:none;background:none;color:var(--ps-muted);padding:2px 4px}.ps-attach-remove:hover{color:var(--ps-foreground)}.ps-attach-button{min-height:40px}.ps-chat-image-strip{display:flex;flex-wrap:wrap;gap:8px;margin:6px 0 10px}.ps-chat-image-thumb{width:96px;height:96px;object-fit:cover;border:1px solid color-mix(in srgb,var(--ps-border) 60%,transparent);border-radius:var(--ps-radius-sm);cursor:pointer}.ps-chat-image-thumb:hover{border-color:var(--ps-modal-selected-border)}.ps-chat-image-thumb.is-pending{display:inline-flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--ps-border) 24%,transparent);color:var(--ps-muted);font-size:24px;cursor:default}.ps-chat-image-thumb.is-pending.is-error{color:var(--ps-foreground)}.ps-binary-preview-image{display:block;max-width:100%;max-height:60vh;margin-top:12px;border:1px solid color-mix(in srgb,var(--ps-border) 60%,transparent);border-radius:var(--ps-radius-sm);cursor:pointer}.ps-send-button{min-height:40px;padding:0 12px}.ps-stop-button{display:inline-flex;align-items:center;justify-content:center;min-height:40px;min-width:40px;padding:0;font-size:15px;line-height:1;border:1px solid color-mix(in srgb,#e5534b 55%,var(--ps-modal-border));border-radius:var(--ps-radius-sm);background:color-mix(in srgb,#e5534b 16%,var(--ps-surface));color:#ff8a80;transition:background .12s ease,border-color .12s ease,transform .12s ease}.ps-stop-button:hover:not(:disabled){background:color-mix(in srgb,#e5534b 30%,var(--ps-surface));border-color:#e5534b}.ps-stop-button:disabled{cursor:default}.ps-stop-button.is-stopping{animation:ps-stop-pulse 1s ease-in-out infinite}@keyframes ps-stop-pulse{50%{opacity:.35}}.ps-send-button.is-inline{min-height:40px;min-width:40px;padding:0;border-radius:var(--ps-radius-sm);font-size:18px;line-height:1;display:inline-flex;align-items:center;justify-content:center}.ps-mobile-nav{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.ps-modal-backdrop{position:fixed;inset:0;background:var(--ps-modal-backdrop);display:flex;align-items:center;justify-content:center;padding:16px;z-index:50}.ps-modal{width:min(900px,100%);max-height:min(86vh,900px);overflow:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y;scrollbar-gutter:stable both-edges;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);background:var(--ps-modal-background);color:var(--ps-modal-foreground);padding:18px;box-shadow:0 30px 80px #0006}.ps-scroll-panel::-webkit-scrollbar,.ps-action-list::-webkit-scrollbar,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar,.ps-modal::-webkit-scrollbar{width:var(--ps-scrollbar-size);height:var(--ps-scrollbar-size)}.ps-scroll-panel::-webkit-scrollbar-track,.ps-action-list::-webkit-scrollbar-track,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-track,.ps-modal::-webkit-scrollbar-track{background:transparent}.ps-scroll-panel::-webkit-scrollbar-thumb,.ps-action-list::-webkit-scrollbar-thumb,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-thumb,.ps-modal::-webkit-scrollbar-thumb{background:var(--ps-scrollbar-thumb);border:2px solid transparent;border-radius:999px;background-clip:padding-box;min-height:28px}.ps-scroll-panel::-webkit-scrollbar-thumb:hover,.ps-action-list::-webkit-scrollbar-thumb:hover,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-thumb:hover,.ps-modal::-webkit-scrollbar-thumb:hover{background:var(--ps-scrollbar-thumb-hover);border:2px solid transparent;background-clip:padding-box}.ps-scroll-panel::-webkit-scrollbar-corner,.ps-action-list::-webkit-scrollbar-corner,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-corner,.ps-modal::-webkit-scrollbar-corner{background:transparent}.ps-modal.is-narrow{width:min(560px,100%)}.ps-modal.is-wide{width:min(980px,100%)}.ps-modal.is-theme-picker{max-height:min(57vh,600px)}.ps-modal-header,.ps-modal-footer{display:flex;align-items:center;justify-content:space-between;gap:12px}.ps-modal-title,.ps-modal-details-title,.ps-filter-title{font-size:16px;font-weight:700}.ps-modal-grid{display:grid;grid-template-columns:minmax(280px,1fr) minmax(260px,.9fr);gap:16px;margin:16px 0}.ps-modal-list,.ps-modal-details{display:flex;flex-direction:column;gap:10px}.ps-modal-list-heading-line,.ps-modal-list-line,.ps-modal-detail-line{display:block;width:100%;text-align:left}.ps-modal-list-button.is-hanging .ps-modal-list-line{padding-left:1.5rem;text-indent:-1.15rem}.ps-modal-list-button.is-disabled{opacity:.55;cursor:not-allowed}.ps-filter-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin:18px 0}.ps-filter-column{display:flex;flex-direction:column;gap:10px}.ps-list-button,.ps-filter-option{justify-content:flex-start;text-align:left}.ps-modal-close{color:var(--ps-muted)}.portal-primary-button:hover,.portal-secondary-button:hover,.ps-toolbar-button:hover,.ps-mini-button:hover,.ps-send-button:hover,.ps-modal-button:hover,.ps-mobile-nav-button:hover,.ps-tab:hover,.ps-filter-option:hover,.ps-list-button:hover,.ps-modal-close:hover,.ps-column-resizer:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--ps-modal-selected-border) 70%,var(--ps-modal-border))}@media(max-width:920px){:root{--ps-font-size-base: 13px;--ps-font-size-dense: 12px}.portal-main{padding:8px}.portal-header{grid-template-columns:minmax(0,1fr) auto;align-items:start;padding:8px 10px}.portal-header-user{gap:8px;align-self:start}.ps-toolbar{align-items:stretch}.ps-toolbar.is-mobile{gap:6px}.ps-toolbar.is-mobile .ps-toolbar-row{flex-wrap:nowrap}.ps-toolbar.is-mobile .ps-icon-button{flex:0 0 auto;padding:8px 9px}.ps-toolbar.is-mobile .ps-toolbar-status{margin-left:auto;max-width:44%;min-width:0;text-align:right}.ps-toolbar-status{max-width:40%;font-size:9px}.ps-chat-focus-rail{flex-wrap:wrap}.ps-chat-focus-status{width:100%;margin-left:0}.ps-chat-focus-overlay{left:8px;right:8px;width:auto;max-width:none;min-width:0}.ps-chat-focus-shell .ps-chat-table-wrap,.ps-chat-focus-shell .ps-md-table-wrap{max-width:100%}.ps-chat-table-wrap.is-fit-width.has-flex-column,.ps-md-table-wrap.is-fit-width.has-flex-column{overflow-x:hidden}.ps-chat-table.is-fit-width.has-flex-column,.ps-md-table.is-fit-width.has-flex-column{display:block;width:100%;min-width:0;table-layout:auto}.ps-chat-table.is-fit-width.has-flex-column colgroup,.ps-chat-table.is-fit-width.has-flex-column thead,.ps-md-table.is-fit-width.has-flex-column colgroup,.ps-md-table.is-fit-width.has-flex-column thead{display:none}.ps-chat-table.is-fit-width.has-flex-column tbody,.ps-md-table.is-fit-width.has-flex-column tbody{display:flex;flex-direction:column}.ps-chat-table.is-fit-width.has-flex-column tr,.ps-md-table.is-fit-width.has-flex-column tr{display:grid;gap:0;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 34%,transparent)}.ps-chat-table.is-fit-width.has-flex-column tr:last-child,.ps-md-table.is-fit-width.has-flex-column tr:last-child{border-bottom:0}.ps-chat-table.is-fit-width.has-flex-column td,.ps-md-table.is-fit-width.has-flex-column td{display:grid;grid-template-columns:minmax(7ch,.34fr) minmax(0,1fr);gap:8px;align-items:start;padding:6px 8px;border-right:0;border-bottom:0;min-width:0;overflow-wrap:anywhere;word-break:break-word}.ps-md-table.is-fit-width.has-flex-column td{padding:7px 10px}.ps-chat-table.is-fit-width.has-flex-column td[data-label]:before,.ps-md-table.is-fit-width.has-flex-column td[data-label]:before{content:attr(data-label);min-width:0;color:var(--ps-muted);font-weight:700;overflow-wrap:anywhere;word-break:break-word}.ps-chat-table.is-fit-width.has-flex-column .ps-table-cell-value,.ps-md-table.is-fit-width.has-flex-column .ps-table-cell-value{min-width:0;overflow-wrap:anywhere;word-break:break-word}.ps-chat-composer{padding:4px 6px 6px}.ps-status-right{display:none}.ps-status-strip{justify-content:flex-start}.ps-modal-grid{grid-template-columns:1fr}.ps-files-grid{grid-template-rows:minmax(160px,1fr) minmax(260px,1.4fr)}.ps-panel-header{flex-wrap:nowrap}.ps-panel-title{min-width:0;flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ps-panel-actions{flex:0 0 auto;flex-wrap:nowrap}.ps-column-resizer{display:none}.ps-mobile-session-pane{max-height:min(22vh,148px)}.ps-mobile-session-pane .ps-panel-body,.ps-mobile-session-pane .ps-action-list{min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y}.ps-modal{max-height:82svh;padding:12px}.ps-modal-header,.ps-modal-footer{flex-wrap:wrap;row-gap:8px}.ps-mobile-pane-fill>.ps-panel .ps-scroll-panel,.ps-mobile-pane-fill>.ps-panel .ps-panel-body{flex:1}}.ps-admin-console{display:flex;flex-direction:column;gap:16px;padding:18px 22px;flex:1;min-height:0;overflow:auto;background:var(--ps-panel-background, var(--ps-background));border:1px solid var(--ps-border);border-radius:6px;margin:8px}.ps-admin-console__header{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:1px solid var(--ps-border);padding-bottom:8px}.ps-admin-console__header h2{margin:0;font-size:16px}.ps-admin-console__identity dl{display:grid;grid-template-columns:max-content 1fr;gap:4px 12px;margin:0;font-size:13px}.ps-admin-console__identity dt{color:var(--ps-muted)}.ps-admin-console__identity dd{margin:0}.ps-admin-console__section{display:flex;flex-direction:column;gap:8px;border:1px solid var(--ps-border);border-radius:6px;padding:12px 14px}.ps-admin-console__section h3{margin:0;font-size:14px}.ps-admin-console__hint{margin:0;color:var(--ps-muted);font-size:12px}.ps-admin-console__status{margin:0;font-size:12px;color:var(--ps-muted)}.ps-admin-console__system-toggle{display:flex;align-items:center;gap:8px;margin:2px 0;font-size:12px;color:var(--ps-fg);cursor:pointer;-webkit-user-select:none;user-select:none}.ps-admin-console__system-toggle input{accent-color:var(--ps-accent, #4a9eff)}.ps-admin-console__status.is-error{color:var(--ps-error, #d33)}.ps-admin-console__error{border:1px solid var(--ps-error, #d33);background:color-mix(in srgb,var(--ps-error, #d33) 10%,transparent);color:var(--ps-error, #d33);padding:8px 10px;border-radius:4px;font-size:12px}.ps-admin-console__form{display:flex;flex-direction:column;gap:8px}.ps-admin-console__form input{font-family:var(--ps-font-mono, monospace);padding:6px 8px;border:1px solid var(--ps-border);border-radius:4px;background:var(--ps-background);color:var(--ps-foreground)}.ps-admin-console__actions{display:flex;gap:8px;flex-wrap:wrap}.portal-dev-banner{display:flex;align-items:center;justify-content:center;gap:8px;padding:6px 12px;border-radius:var(--ps-radius-sm);background:repeating-linear-gradient(-45deg,#eab30838,#eab30838 12px,#eab3081a 12px 24px);border:1px solid rgba(234,179,8,.55);color:var(--ps-foreground);font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;margin-bottom:14px}.portal-dev-banner.is-fixed{border-radius:0;border-left:none;border-right:none;border-top:none;margin-bottom:0}.portal-dev-persona-list{display:flex;flex-direction:column;gap:8px;margin-top:20px}.portal-dev-persona-button{display:flex;align-items:center;gap:12px;width:100%;padding:10px 14px;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);color:var(--ps-foreground);cursor:pointer;text-align:left;transition:background .12s ease,border-color .12s ease}.portal-dev-persona-button:hover{background:var(--ps-highlight-background);color:var(--ps-highlight-foreground)}.portal-dev-persona-initials{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background:color-mix(in srgb,var(--ps-foreground) 12%,transparent);font-size:11px;font-weight:700;flex:none}.portal-dev-persona-name{flex:1;font-weight:600}.portal-dev-persona-role{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ps-muted)}.portal-dev-persona-role.is-admin{color:#f59e0b;font-weight:700}.ps-access-bar{display:flex;align-items:center;gap:10px;padding:4px 6px 8px;font-size:12px;color:var(--ps-muted)}.ps-access-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid var(--ps-modal-border);background:color-mix(in srgb,var(--ps-surface) 92%,transparent);font-weight:600;white-space:nowrap}.ps-access-owner{font-style:italic}.ps-access-readonly{color:#f59e0b;font-weight:600;text-transform:uppercase;letter-spacing:.06em;font-size:11px}.ps-access-spacer{flex:1}.ps-composer-readonly{padding:12px 14px;border:1px dashed var(--ps-modal-border);border-radius:var(--ps-radius-sm);color:var(--ps-muted);font-size:13px;line-height:1.5}.ps-share-overlay{position:fixed;inset:0;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:1000;padding:24px}.ps-share-modal{width:min(440px,100%);height:min(540px,85vh);display:flex;flex-direction:column;overflow-y:auto;background:var(--ps-modal-background, var(--ps-surface));border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);box-shadow:0 28px 80px #0006;padding:18px;color:var(--ps-foreground)}.ps-share-modal-head{display:flex;align-items:center;justify-content:space-between;font-size:16px;font-weight:700;margin-bottom:12px}.ps-link-modal{width:min(480px,100%);max-height:80vh;overflow-y:auto;background:var(--ps-modal-background, var(--ps-surface));border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);box-shadow:0 28px 80px #0006;padding:18px;color:var(--ps-foreground)}.ps-link-row{display:flex;gap:8px;margin-top:12px}.ps-link-input{flex:1;min-width:0;font-family:var(--ps-mono, monospace);font-size:12px;padding:8px 10px;color:var(--ps-foreground);background:var(--ps-background);border:1px solid var(--ps-border);border-radius:var(--ps-radius-sm)}.ps-link-warn{margin-top:12px;padding:8px 10px;font-size:12px;color:var(--ps-foreground);background:color-mix(in srgb,var(--ps-warning, #d0a215) 16%,transparent);border:1px solid color-mix(in srgb,var(--ps-warning, #d0a215) 45%,transparent);border-radius:var(--ps-radius-sm)}.ps-share-section-label{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ps-muted);margin:14px 0 6px}.ps-manage-tabs{display:flex;gap:6px;margin-bottom:12px}.ps-manage-tab{flex:1 1 0;text-align:center;appearance:none;background:transparent;color:var(--ps-muted);font:inherit;font-size:13px;padding:8px 14px;cursor:pointer;border:1px solid transparent;border-radius:8px}.ps-manage-tab:hover{color:var(--ps-foreground)}.ps-manage-tab:focus{outline:none}.ps-manage-tab:focus-visible{outline:none;color:var(--ps-foreground);border-color:color-mix(in srgb,var(--ps-highlight-background) 45%,transparent)}.ps-manage-tab.is-active{color:var(--ps-foreground);font-weight:600;border-color:var(--ps-highlight-background);background:color-mix(in srgb,var(--ps-highlight-background) 14%,transparent)}.ps-manage-model-current{flex:1;font-family:var(--ps-mono, monospace);font-size:13px;color:var(--ps-foreground);overflow-wrap:anywhere}.ps-manage-apply-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px;padding-top:12px;border-top:1px solid var(--ps-border)}.ps-mini-button.ps-manage-apply:disabled{background:transparent;color:var(--ps-muted);border-color:color-mix(in srgb,var(--ps-border) 60%,transparent);opacity:.5;cursor:not-allowed}.ps-share-radio{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--ps-radius-sm);cursor:pointer;font-size:13px}.ps-share-radio.is-active{background:color-mix(in srgb,var(--ps-highlight-background) 22%,transparent)}.ps-share-radio-glyph{width:18px;text-align:center}.ps-share-radio-hint{color:var(--ps-muted);font-size:11px;margin-left:auto}.ps-share-grant-row,.ps-share-add-row,.ps-share-link-row{display:flex;align-items:center;gap:8px;padding:5px 0}.ps-share-link-row .ps-share-section-sub{margin:0}.ps-share-grant-name{flex:1;font-size:13px}.ps-share-grant-access{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ps-muted)}.ps-share-empty{color:var(--ps-muted);font-size:12px;padding:4px 0}.ps-share-add-input{flex:1;padding:6px 8px;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-sm);background:var(--ps-background);color:var(--ps-foreground);font-size:13px}.ps-share-add-select{padding:6px 8px;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-sm);background:var(--ps-background);color:var(--ps-foreground);font-size:13px}.ps-share-error{margin-top:10px;color:#ef4444;font-size:12px}.ps-share-foot-hint{margin-top:14px;color:var(--ps-muted);font-size:11px}.ps-share-section-sub{color:var(--ps-muted);font-size:11px;line-height:1.4;margin:-2px 0 6px}.ps-share-add-wrap{position:relative}.ps-share-suggestions{display:flex;flex-direction:column;margin-top:4px;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-sm);background:var(--ps-background);max-height:108px;overflow-y:auto}.ps-share-suggestion{display:flex;align-items:baseline;gap:8px;padding:7px 10px;border:none;background:transparent;color:var(--ps-foreground);cursor:pointer;text-align:left;font-size:13px}.ps-share-suggestion:hover{background:var(--ps-highlight-background);color:var(--ps-highlight-foreground)}.ps-share-suggestion-name{font-weight:600}.ps-share-suggestion-email{color:var(--ps-muted);font-size:11px}.ps-share-suggestion:hover .ps-share-suggestion-email{color:inherit}.ps-icon-button{position:relative;min-width:44px;padding:8px 12px;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:manipulation}.ps-icon-button-glyph{display:inline-flex;align-items:center;justify-content:center;font-size:20px;line-height:1;letter-spacing:-.02em}.ps-icon-tooltip{position:fixed;max-width:220px;white-space:normal;text-align:center;background:var(--ps-foreground);color:var(--ps-background);padding:5px 9px;border-radius:var(--ps-radius-sm);font-size:11px;line-height:1.35;font-weight:500;z-index:100000;pointer-events:none;box-shadow:0 6px 18px #0006}.ps-icon-tooltip.is-below{transform:translate(-50%)}.ps-icon-tooltip.is-above{transform:translate(-50%,-100%)}.ps-share-glyph{width:18px;height:18px;display:block}
|