@aryacode/dsh-aryacode-polish 0.8.1 → 0.8.2
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/CHANGELOG.md +14 -3
- package/client/face.js +125 -110
- package/lib/client.js +126 -111
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## [0.8.2] - 2026-10-28
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- The camera floated over the panel with metrics of its own, so it read as a
|
|
9
|
+
foreign control. It now mounts into the docking kit's chrome seat beside the
|
|
10
|
+
Shell's own presentation and collapse buttons, wearing that cluster's 28px box,
|
|
11
|
+
15px glyph, hover state and tooltip, and it follows the strip through docked,
|
|
12
|
+
split and fullscreen layouts.
|
|
13
|
+
|
|
3
14
|
## [0.8.1] - 2026-10-28
|
|
4
15
|
|
|
5
16
|
### Changed
|
|
6
17
|
|
|
7
|
-
- The camera moved out of the composer row and
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
- The camera moved out of the composer row and next to the right panel's own
|
|
19
|
+
controls. The composer dock entry stays, invisibly, to publish the session
|
|
20
|
+
context the button needs.
|
|
10
21
|
|
|
11
22
|
## [0.8.0] - 2026-10-28
|
|
12
23
|
|
package/client/face.js
CHANGED
|
@@ -1572,7 +1572,6 @@ function shotName() {
|
|
|
1572
1572
|
+ '-' + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds()) + '.png'
|
|
1573
1573
|
}
|
|
1574
1574
|
|
|
1575
|
-
const SHOT_GLYPH_STYLE = { width: '14px', height: '14px', flex: 'none', display: 'block' }
|
|
1576
1575
|
|
|
1577
1576
|
/**
|
|
1578
1577
|
* Camera glyph, drawn here so the face depends on no icon package.
|
|
@@ -1581,51 +1580,94 @@ const SHOT_GLYPH_STYLE = { width: '14px', height: '14px', flex: 'none', display:
|
|
|
1581
1580
|
function CameraGlyph() {
|
|
1582
1581
|
return React.createElement('svg', {
|
|
1583
1582
|
viewBox: '0 0 16 16',
|
|
1584
|
-
width:
|
|
1585
|
-
height:
|
|
1583
|
+
width: 15,
|
|
1584
|
+
height: 15,
|
|
1585
|
+
fill: 'none',
|
|
1586
|
+
stroke: 'currentColor',
|
|
1587
|
+
strokeWidth: 1,
|
|
1588
|
+
strokeLinecap: 'round',
|
|
1589
|
+
strokeLinejoin: 'round',
|
|
1590
|
+
'aria-hidden': 'true',
|
|
1591
|
+
},
|
|
1592
|
+
React.createElement('path', { d: 'M2.6 5.4h1.9l1-1.5h5l1 1.5h1.9a1 1 0 0 1 1 1v5.7a1 1 0 0 1-1 1H2.6a1 1 0 0 1-1-1V6.4a1 1 0 0 1 1-1Z' }),
|
|
1593
|
+
React.createElement('circle', { cx: 8, cy: 9.3, r: 2.5 }))
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/** Confirmation tick, drawn at the strip's own glyph size. */
|
|
1597
|
+
function CheckGlyph() {
|
|
1598
|
+
return React.createElement('svg', {
|
|
1599
|
+
viewBox: '0 0 16 16',
|
|
1600
|
+
width: 15,
|
|
1601
|
+
height: 15,
|
|
1602
|
+
fill: 'none',
|
|
1603
|
+
stroke: 'currentColor',
|
|
1604
|
+
strokeWidth: 1.4,
|
|
1605
|
+
strokeLinecap: 'round',
|
|
1606
|
+
strokeLinejoin: 'round',
|
|
1607
|
+
'aria-hidden': 'true',
|
|
1608
|
+
},
|
|
1609
|
+
React.createElement('path', { d: 'M3.4 8.6l3 3 6.2-6.6' }))
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/** Refusal mark, drawn at the strip's own glyph size. */
|
|
1613
|
+
function BangGlyph() {
|
|
1614
|
+
return React.createElement('svg', {
|
|
1615
|
+
viewBox: '0 0 16 16',
|
|
1616
|
+
width: 15,
|
|
1617
|
+
height: 15,
|
|
1586
1618
|
fill: 'none',
|
|
1587
1619
|
stroke: 'currentColor',
|
|
1588
|
-
strokeWidth: 1.
|
|
1620
|
+
strokeWidth: 1.4,
|
|
1589
1621
|
strokeLinecap: 'round',
|
|
1590
1622
|
strokeLinejoin: 'round',
|
|
1591
1623
|
'aria-hidden': 'true',
|
|
1592
|
-
style: SHOT_GLYPH_STYLE,
|
|
1593
1624
|
},
|
|
1594
|
-
React.createElement('path', { d: '
|
|
1595
|
-
React.createElement('
|
|
1625
|
+
React.createElement('path', { d: 'M8 3.6v5.2' }),
|
|
1626
|
+
React.createElement('path', { d: 'M8 12v.4' }))
|
|
1596
1627
|
}
|
|
1597
1628
|
|
|
1598
1629
|
/**
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1630
|
+
* The Shell has no header slot for the panel, so the camera rides the docking
|
|
1631
|
+
* kit's chrome seat: the element at the end of the top-right pane's tab strip
|
|
1632
|
+
* that already holds the presentation and collapse controls. Portalling into
|
|
1633
|
+
* that element, instead of floating over the panel at fixed coordinates, is what
|
|
1634
|
+
* makes the button share the cluster's gap, its vertical centring and whatever
|
|
1635
|
+
* geometry the strip has docked, fullscreened or split.
|
|
1601
1636
|
*/
|
|
1602
|
-
const
|
|
1603
|
-
const SHOT_ANCHOR_BUTTON = 28
|
|
1604
|
-
const SHOT_ANCHOR_CONTROLS = 96
|
|
1637
|
+
const SHOT_CHROME_SELECTOR = '[data-dockkit-strip-chrome]'
|
|
1605
1638
|
|
|
1606
1639
|
/**
|
|
1607
|
-
*
|
|
1608
|
-
*
|
|
1640
|
+
* Mirrors the Shell's own strip control: a 28px box with 6px padding around a
|
|
1641
|
+
* 15px glyph, secondary label colour, transparent until hovered.
|
|
1609
1642
|
*/
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1643
|
+
const SHOT_BUTTON_STYLE = {
|
|
1644
|
+
display: 'inline-flex',
|
|
1645
|
+
flex: 'none',
|
|
1646
|
+
alignItems: 'center',
|
|
1647
|
+
justifyContent: 'center',
|
|
1648
|
+
width: '28px',
|
|
1649
|
+
height: '28px',
|
|
1650
|
+
padding: '6px',
|
|
1651
|
+
color: 'var(--dsw-alias-label-secondary, #61666b)',
|
|
1652
|
+
lineHeight: 1,
|
|
1653
|
+
background: 'transparent',
|
|
1654
|
+
border: 'none',
|
|
1655
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1656
|
+
cursor: 'pointer',
|
|
1657
|
+
// Ahead of the Shell's own two controls: the last thing you would reach for,
|
|
1658
|
+
// and never between them and the panel edge.
|
|
1659
|
+
order: -1,
|
|
1619
1660
|
}
|
|
1620
1661
|
|
|
1621
1662
|
/**
|
|
1622
|
-
* The composer context the
|
|
1663
|
+
* The composer context the chrome button attaches through.
|
|
1623
1664
|
*
|
|
1624
|
-
* The camera
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1665
|
+
* The camera renders inside the panel strip, which is root chrome, but attaching
|
|
1666
|
+
* an image needs the *session-scoped* composer props (`sessionId` and
|
|
1667
|
+
* `inputActions`), which only a session slot receives. The composer dock is
|
|
1627
1668
|
* therefore kept as an invisible publisher and this store carries its context up
|
|
1628
|
-
* to the
|
|
1669
|
+
* to the chrome button; with no open Session the store is empty and the button
|
|
1670
|
+
* does not render.
|
|
1629
1671
|
*/
|
|
1630
1672
|
const shotStore = (() => {
|
|
1631
1673
|
const listeners = new Set()
|
|
@@ -1643,24 +1685,8 @@ const shotStore = (() => {
|
|
|
1643
1685
|
}
|
|
1644
1686
|
})()
|
|
1645
1687
|
|
|
1646
|
-
const SHOT_BUTTON_STYLE = {
|
|
1647
|
-
position: 'fixed',
|
|
1648
|
-
display: 'inline-flex',
|
|
1649
|
-
alignItems: 'center',
|
|
1650
|
-
justifyContent: 'center',
|
|
1651
|
-
width: SHOT_ANCHOR_BUTTON + 'px',
|
|
1652
|
-
height: SHOT_ANCHOR_BUTTON + 'px',
|
|
1653
|
-
padding: '0',
|
|
1654
|
-
border: 'none',
|
|
1655
|
-
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1656
|
-
background: 'transparent',
|
|
1657
|
-
color: 'var(--dsw-alias-label-tertiary, #61666b)',
|
|
1658
|
-
cursor: 'pointer',
|
|
1659
|
-
zIndex: 40,
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
1688
|
/**
|
|
1663
|
-
* Invisible composer-dock entry that publishes the session context the
|
|
1689
|
+
* Invisible composer-dock entry that publishes the session context the chrome
|
|
1664
1690
|
* button needs, and nothing else.
|
|
1665
1691
|
* @param props - session slot props (`sessionId`, `inputActions`) plus the
|
|
1666
1692
|
* late `conversation` lookup.
|
|
@@ -1679,29 +1705,35 @@ function PanelShotContext(props) {
|
|
|
1679
1705
|
}
|
|
1680
1706
|
|
|
1681
1707
|
/**
|
|
1682
|
-
* Camera button
|
|
1683
|
-
*
|
|
1684
|
-
* @param props - slot props plus the late `conversation` lookup.
|
|
1685
|
-
* @returns the button, or null while there is
|
|
1686
|
-
* attach.
|
|
1708
|
+
* Camera button in the right panel's tab strip: photographs the open tab, copies
|
|
1709
|
+
* it, and attaches it to the message being written.
|
|
1710
|
+
* @param props - overlay slot props plus the late `conversation` lookup.
|
|
1711
|
+
* @returns the portalled button, or null while there is no panel or no Session.
|
|
1687
1712
|
*/
|
|
1688
1713
|
function PanelShotButton(props) {
|
|
1689
1714
|
const on = useSwitch(props.configForms, 'panelScreenshot', true)
|
|
1690
1715
|
const context = React.useSyncExternalStore(shotStore.subscribe, shotStore.get)
|
|
1691
|
-
const [
|
|
1716
|
+
const [host, setHost] = React.useState(undefined)
|
|
1692
1717
|
const [phase, setPhase] = React.useState('idle')
|
|
1693
1718
|
const [note, setNote] = React.useState('')
|
|
1694
1719
|
React.useEffect(() => {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
//
|
|
1698
|
-
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1720
|
+
// The strip is created, destroyed and re-created as panes open, split and
|
|
1721
|
+
// close, so follow the document rather than measuring it once. Frames are
|
|
1722
|
+
// coalesced because React commits fire many mutations in a row.
|
|
1723
|
+
let frame = 0
|
|
1724
|
+
const find = () => {
|
|
1725
|
+
frame = 0
|
|
1726
|
+
setHost(document.querySelector(SHOT_CHROME_SELECTOR) ?? undefined)
|
|
1727
|
+
}
|
|
1728
|
+
const schedule = () => {
|
|
1729
|
+
if (frame === 0) frame = window.requestAnimationFrame(find)
|
|
1730
|
+
}
|
|
1731
|
+
find()
|
|
1732
|
+
const observer = new MutationObserver(schedule)
|
|
1733
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
1702
1734
|
return () => {
|
|
1735
|
+
if (frame !== 0) window.cancelAnimationFrame(frame)
|
|
1703
1736
|
observer.disconnect()
|
|
1704
|
-
window.removeEventListener('resize', measure)
|
|
1705
1737
|
}
|
|
1706
1738
|
}, [])
|
|
1707
1739
|
React.useEffect(() => {
|
|
@@ -1712,7 +1744,7 @@ function PanelShotButton(props) {
|
|
|
1712
1744
|
}, 4000)
|
|
1713
1745
|
return () => clearTimeout(timer)
|
|
1714
1746
|
}, [note])
|
|
1715
|
-
if (!on || context === undefined ||
|
|
1747
|
+
if (!on || context === undefined || host === undefined) return null
|
|
1716
1748
|
const take = async () => {
|
|
1717
1749
|
if (phase === 'busy') return
|
|
1718
1750
|
const node = panelTarget()
|
|
@@ -1748,63 +1780,46 @@ function PanelShotButton(props) {
|
|
|
1748
1780
|
throw new Error('the composer refused the image')
|
|
1749
1781
|
}
|
|
1750
1782
|
setPhase('done')
|
|
1751
|
-
setNote(copied ? '
|
|
1783
|
+
setNote(copied ? 'Right panel copied and attached' : 'Right panel attached, clipboard refused')
|
|
1752
1784
|
} catch (error) {
|
|
1753
1785
|
setPhase('failed')
|
|
1754
1786
|
setNote(error instanceof Error ? error.message : String(error))
|
|
1755
1787
|
console.warn('[aryacode-polish] panel screenshot failed:', error)
|
|
1756
1788
|
}
|
|
1757
1789
|
}
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
fontSize: '11px',
|
|
1783
|
-
},
|
|
1790
|
+
const tip = note !== ''
|
|
1791
|
+
? note
|
|
1792
|
+
: phase === 'busy' ? 'Capturing the right panel…' : 'Screenshot this panel into the chat (also copies it)'
|
|
1793
|
+
const glyph = phase === 'done'
|
|
1794
|
+
? React.createElement(CheckGlyph, null)
|
|
1795
|
+
: phase === 'failed'
|
|
1796
|
+
? React.createElement(BangGlyph, null)
|
|
1797
|
+
: React.createElement(CameraGlyph, null)
|
|
1798
|
+
const button = React.createElement('button', {
|
|
1799
|
+
type: 'button',
|
|
1800
|
+
'data-polish-shot': phase,
|
|
1801
|
+
'aria-label': 'Screenshot this panel',
|
|
1802
|
+
disabled: phase === 'busy',
|
|
1803
|
+
onClick: () => { void take() },
|
|
1804
|
+
onMouseEnter: (event) => { event.currentTarget.style.background = 'var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.06))' },
|
|
1805
|
+
onMouseLeave: (event) => { event.currentTarget.style.background = 'transparent' },
|
|
1806
|
+
style: {
|
|
1807
|
+
...SHOT_BUTTON_STYLE,
|
|
1808
|
+
color: phase === 'failed'
|
|
1809
|
+
? 'var(--dsw-alias-label-error, #b42318)'
|
|
1810
|
+
: phase === 'done'
|
|
1811
|
+
? 'var(--dsw-alias-label-primary, #111418)'
|
|
1812
|
+
: SHOT_BUTTON_STYLE.color,
|
|
1813
|
+
cursor: phase === 'busy' ? 'progress' : 'pointer',
|
|
1784
1814
|
},
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
?
|
|
1790
|
-
: React.createElement('
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
position: 'fixed',
|
|
1794
|
-
top: anchor.top + SHOT_ANCHOR_BUTTON + 6 + 'px',
|
|
1795
|
-
left: Math.max(12, anchor.left - 140) + 'px',
|
|
1796
|
-
maxWidth: '260px',
|
|
1797
|
-
padding: '3px 8px',
|
|
1798
|
-
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1799
|
-
background: 'Canvas',
|
|
1800
|
-
boxShadow: 'var(--dsw-elevation-prominent, 0 3px 8px rgba(0,0,0,0.12))',
|
|
1801
|
-
color: phase === 'failed' ? 'var(--dsw-alias-label-error, #b42318)' : 'var(--dsw-alias-label-secondary, #61666b)',
|
|
1802
|
-
fontSize: '12px',
|
|
1803
|
-
lineHeight: '18px',
|
|
1804
|
-
whiteSpace: 'nowrap',
|
|
1805
|
-
zIndex: 41,
|
|
1806
|
-
},
|
|
1807
|
-
}, note))
|
|
1815
|
+
}, glyph)
|
|
1816
|
+
const Tooltip = primitives === undefined ? undefined : primitives.Tooltip
|
|
1817
|
+
return ReactDOM.createPortal(
|
|
1818
|
+
Tooltip === undefined
|
|
1819
|
+
? button
|
|
1820
|
+
: React.createElement(Tooltip, { label: tip, side: 'bottom', delayMs: 500 }, button),
|
|
1821
|
+
host,
|
|
1822
|
+
)
|
|
1808
1823
|
}
|
|
1809
1824
|
|
|
1810
1825
|
/**
|
package/lib/client.js
CHANGED
|
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({
|
|
|
47
47
|
]
|
|
48
48
|
const ROW_ID = "polish"
|
|
49
49
|
const ROW_KEY = "@aryacode/dsh-aryacode-polish" + '#' + ROW_ID
|
|
50
|
-
const CLIENT_VERSION = "0.8.
|
|
50
|
+
const CLIENT_VERSION = "0.8.2"
|
|
51
51
|
|
|
52
52
|
/* html2canvas 1.4.1 — MIT, vendored from vendor/html2canvas.min.js.
|
|
53
53
|
* A UMD build, so it assigns itself to globalThis; face.js reads it lazily. */
|
|
@@ -1768,7 +1768,6 @@ window.__ModuleLoader__.load({
|
|
|
1768
1768
|
+ '-' + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds()) + '.png'
|
|
1769
1769
|
}
|
|
1770
1770
|
|
|
1771
|
-
const SHOT_GLYPH_STYLE = { width: '14px', height: '14px', flex: 'none', display: 'block' }
|
|
1772
1771
|
|
|
1773
1772
|
/**
|
|
1774
1773
|
* Camera glyph, drawn here so the face depends on no icon package.
|
|
@@ -1777,51 +1776,94 @@ window.__ModuleLoader__.load({
|
|
|
1777
1776
|
function CameraGlyph() {
|
|
1778
1777
|
return React.createElement('svg', {
|
|
1779
1778
|
viewBox: '0 0 16 16',
|
|
1780
|
-
width:
|
|
1781
|
-
height:
|
|
1779
|
+
width: 15,
|
|
1780
|
+
height: 15,
|
|
1781
|
+
fill: 'none',
|
|
1782
|
+
stroke: 'currentColor',
|
|
1783
|
+
strokeWidth: 1,
|
|
1784
|
+
strokeLinecap: 'round',
|
|
1785
|
+
strokeLinejoin: 'round',
|
|
1786
|
+
'aria-hidden': 'true',
|
|
1787
|
+
},
|
|
1788
|
+
React.createElement('path', { d: 'M2.6 5.4h1.9l1-1.5h5l1 1.5h1.9a1 1 0 0 1 1 1v5.7a1 1 0 0 1-1 1H2.6a1 1 0 0 1-1-1V6.4a1 1 0 0 1 1-1Z' }),
|
|
1789
|
+
React.createElement('circle', { cx: 8, cy: 9.3, r: 2.5 }))
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
/** Confirmation tick, drawn at the strip's own glyph size. */
|
|
1793
|
+
function CheckGlyph() {
|
|
1794
|
+
return React.createElement('svg', {
|
|
1795
|
+
viewBox: '0 0 16 16',
|
|
1796
|
+
width: 15,
|
|
1797
|
+
height: 15,
|
|
1798
|
+
fill: 'none',
|
|
1799
|
+
stroke: 'currentColor',
|
|
1800
|
+
strokeWidth: 1.4,
|
|
1801
|
+
strokeLinecap: 'round',
|
|
1802
|
+
strokeLinejoin: 'round',
|
|
1803
|
+
'aria-hidden': 'true',
|
|
1804
|
+
},
|
|
1805
|
+
React.createElement('path', { d: 'M3.4 8.6l3 3 6.2-6.6' }))
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
/** Refusal mark, drawn at the strip's own glyph size. */
|
|
1809
|
+
function BangGlyph() {
|
|
1810
|
+
return React.createElement('svg', {
|
|
1811
|
+
viewBox: '0 0 16 16',
|
|
1812
|
+
width: 15,
|
|
1813
|
+
height: 15,
|
|
1782
1814
|
fill: 'none',
|
|
1783
1815
|
stroke: 'currentColor',
|
|
1784
|
-
strokeWidth: 1.
|
|
1816
|
+
strokeWidth: 1.4,
|
|
1785
1817
|
strokeLinecap: 'round',
|
|
1786
1818
|
strokeLinejoin: 'round',
|
|
1787
1819
|
'aria-hidden': 'true',
|
|
1788
|
-
style: SHOT_GLYPH_STYLE,
|
|
1789
1820
|
},
|
|
1790
|
-
React.createElement('path', { d: '
|
|
1791
|
-
React.createElement('
|
|
1821
|
+
React.createElement('path', { d: 'M8 3.6v5.2' }),
|
|
1822
|
+
React.createElement('path', { d: 'M8 12v.4' }))
|
|
1792
1823
|
}
|
|
1793
1824
|
|
|
1794
1825
|
/**
|
|
1795
|
-
*
|
|
1796
|
-
*
|
|
1826
|
+
* The Shell has no header slot for the panel, so the camera rides the docking
|
|
1827
|
+
* kit's chrome seat: the element at the end of the top-right pane's tab strip
|
|
1828
|
+
* that already holds the presentation and collapse controls. Portalling into
|
|
1829
|
+
* that element, instead of floating over the panel at fixed coordinates, is what
|
|
1830
|
+
* makes the button share the cluster's gap, its vertical centring and whatever
|
|
1831
|
+
* geometry the strip has docked, fullscreened or split.
|
|
1797
1832
|
*/
|
|
1798
|
-
const
|
|
1799
|
-
const SHOT_ANCHOR_BUTTON = 28
|
|
1800
|
-
const SHOT_ANCHOR_CONTROLS = 96
|
|
1833
|
+
const SHOT_CHROME_SELECTOR = '[data-dockkit-strip-chrome]'
|
|
1801
1834
|
|
|
1802
1835
|
/**
|
|
1803
|
-
*
|
|
1804
|
-
*
|
|
1836
|
+
* Mirrors the Shell's own strip control: a 28px box with 6px padding around a
|
|
1837
|
+
* 15px glyph, secondary label colour, transparent until hovered.
|
|
1805
1838
|
*/
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1839
|
+
const SHOT_BUTTON_STYLE = {
|
|
1840
|
+
display: 'inline-flex',
|
|
1841
|
+
flex: 'none',
|
|
1842
|
+
alignItems: 'center',
|
|
1843
|
+
justifyContent: 'center',
|
|
1844
|
+
width: '28px',
|
|
1845
|
+
height: '28px',
|
|
1846
|
+
padding: '6px',
|
|
1847
|
+
color: 'var(--dsw-alias-label-secondary, #61666b)',
|
|
1848
|
+
lineHeight: 1,
|
|
1849
|
+
background: 'transparent',
|
|
1850
|
+
border: 'none',
|
|
1851
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1852
|
+
cursor: 'pointer',
|
|
1853
|
+
// Ahead of the Shell's own two controls: the last thing you would reach for,
|
|
1854
|
+
// and never between them and the panel edge.
|
|
1855
|
+
order: -1,
|
|
1815
1856
|
}
|
|
1816
1857
|
|
|
1817
1858
|
/**
|
|
1818
|
-
* The composer context the
|
|
1859
|
+
* The composer context the chrome button attaches through.
|
|
1819
1860
|
*
|
|
1820
|
-
* The camera
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1861
|
+
* The camera renders inside the panel strip, which is root chrome, but attaching
|
|
1862
|
+
* an image needs the *session-scoped* composer props (`sessionId` and
|
|
1863
|
+
* `inputActions`), which only a session slot receives. The composer dock is
|
|
1823
1864
|
* therefore kept as an invisible publisher and this store carries its context up
|
|
1824
|
-
* to the
|
|
1865
|
+
* to the chrome button; with no open Session the store is empty and the button
|
|
1866
|
+
* does not render.
|
|
1825
1867
|
*/
|
|
1826
1868
|
const shotStore = (() => {
|
|
1827
1869
|
const listeners = new Set()
|
|
@@ -1839,24 +1881,8 @@ window.__ModuleLoader__.load({
|
|
|
1839
1881
|
}
|
|
1840
1882
|
})()
|
|
1841
1883
|
|
|
1842
|
-
const SHOT_BUTTON_STYLE = {
|
|
1843
|
-
position: 'fixed',
|
|
1844
|
-
display: 'inline-flex',
|
|
1845
|
-
alignItems: 'center',
|
|
1846
|
-
justifyContent: 'center',
|
|
1847
|
-
width: SHOT_ANCHOR_BUTTON + 'px',
|
|
1848
|
-
height: SHOT_ANCHOR_BUTTON + 'px',
|
|
1849
|
-
padding: '0',
|
|
1850
|
-
border: 'none',
|
|
1851
|
-
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1852
|
-
background: 'transparent',
|
|
1853
|
-
color: 'var(--dsw-alias-label-tertiary, #61666b)',
|
|
1854
|
-
cursor: 'pointer',
|
|
1855
|
-
zIndex: 40,
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
1884
|
/**
|
|
1859
|
-
* Invisible composer-dock entry that publishes the session context the
|
|
1885
|
+
* Invisible composer-dock entry that publishes the session context the chrome
|
|
1860
1886
|
* button needs, and nothing else.
|
|
1861
1887
|
* @param props - session slot props (`sessionId`, `inputActions`) plus the
|
|
1862
1888
|
* late `conversation` lookup.
|
|
@@ -1875,29 +1901,35 @@ window.__ModuleLoader__.load({
|
|
|
1875
1901
|
}
|
|
1876
1902
|
|
|
1877
1903
|
/**
|
|
1878
|
-
* Camera button
|
|
1879
|
-
*
|
|
1880
|
-
* @param props - slot props plus the late `conversation` lookup.
|
|
1881
|
-
* @returns the button, or null while there is
|
|
1882
|
-
* attach.
|
|
1904
|
+
* Camera button in the right panel's tab strip: photographs the open tab, copies
|
|
1905
|
+
* it, and attaches it to the message being written.
|
|
1906
|
+
* @param props - overlay slot props plus the late `conversation` lookup.
|
|
1907
|
+
* @returns the portalled button, or null while there is no panel or no Session.
|
|
1883
1908
|
*/
|
|
1884
1909
|
function PanelShotButton(props) {
|
|
1885
1910
|
const on = useSwitch(props.configForms, 'panelScreenshot', true)
|
|
1886
1911
|
const context = React.useSyncExternalStore(shotStore.subscribe, shotStore.get)
|
|
1887
|
-
const [
|
|
1912
|
+
const [host, setHost] = React.useState(undefined)
|
|
1888
1913
|
const [phase, setPhase] = React.useState('idle')
|
|
1889
1914
|
const [note, setNote] = React.useState('')
|
|
1890
1915
|
React.useEffect(() => {
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
//
|
|
1894
|
-
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
|
|
1916
|
+
// The strip is created, destroyed and re-created as panes open, split and
|
|
1917
|
+
// close, so follow the document rather than measuring it once. Frames are
|
|
1918
|
+
// coalesced because React commits fire many mutations in a row.
|
|
1919
|
+
let frame = 0
|
|
1920
|
+
const find = () => {
|
|
1921
|
+
frame = 0
|
|
1922
|
+
setHost(document.querySelector(SHOT_CHROME_SELECTOR) ?? undefined)
|
|
1923
|
+
}
|
|
1924
|
+
const schedule = () => {
|
|
1925
|
+
if (frame === 0) frame = window.requestAnimationFrame(find)
|
|
1926
|
+
}
|
|
1927
|
+
find()
|
|
1928
|
+
const observer = new MutationObserver(schedule)
|
|
1929
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
1898
1930
|
return () => {
|
|
1931
|
+
if (frame !== 0) window.cancelAnimationFrame(frame)
|
|
1899
1932
|
observer.disconnect()
|
|
1900
|
-
window.removeEventListener('resize', measure)
|
|
1901
1933
|
}
|
|
1902
1934
|
}, [])
|
|
1903
1935
|
React.useEffect(() => {
|
|
@@ -1908,7 +1940,7 @@ window.__ModuleLoader__.load({
|
|
|
1908
1940
|
}, 4000)
|
|
1909
1941
|
return () => clearTimeout(timer)
|
|
1910
1942
|
}, [note])
|
|
1911
|
-
if (!on || context === undefined ||
|
|
1943
|
+
if (!on || context === undefined || host === undefined) return null
|
|
1912
1944
|
const take = async () => {
|
|
1913
1945
|
if (phase === 'busy') return
|
|
1914
1946
|
const node = panelTarget()
|
|
@@ -1944,63 +1976,46 @@ window.__ModuleLoader__.load({
|
|
|
1944
1976
|
throw new Error('the composer refused the image')
|
|
1945
1977
|
}
|
|
1946
1978
|
setPhase('done')
|
|
1947
|
-
setNote(copied ? '
|
|
1979
|
+
setNote(copied ? 'Right panel copied and attached' : 'Right panel attached, clipboard refused')
|
|
1948
1980
|
} catch (error) {
|
|
1949
1981
|
setPhase('failed')
|
|
1950
1982
|
setNote(error instanceof Error ? error.message : String(error))
|
|
1951
1983
|
console.warn('[aryacode-polish] panel screenshot failed:', error)
|
|
1952
1984
|
}
|
|
1953
1985
|
}
|
|
1954
|
-
const
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
fontSize: '11px',
|
|
1979
|
-
},
|
|
1986
|
+
const tip = note !== ''
|
|
1987
|
+
? note
|
|
1988
|
+
: phase === 'busy' ? 'Capturing the right panel…' : 'Screenshot this panel into the chat (also copies it)'
|
|
1989
|
+
const glyph = phase === 'done'
|
|
1990
|
+
? React.createElement(CheckGlyph, null)
|
|
1991
|
+
: phase === 'failed'
|
|
1992
|
+
? React.createElement(BangGlyph, null)
|
|
1993
|
+
: React.createElement(CameraGlyph, null)
|
|
1994
|
+
const button = React.createElement('button', {
|
|
1995
|
+
type: 'button',
|
|
1996
|
+
'data-polish-shot': phase,
|
|
1997
|
+
'aria-label': 'Screenshot this panel',
|
|
1998
|
+
disabled: phase === 'busy',
|
|
1999
|
+
onClick: () => { void take() },
|
|
2000
|
+
onMouseEnter: (event) => { event.currentTarget.style.background = 'var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.06))' },
|
|
2001
|
+
onMouseLeave: (event) => { event.currentTarget.style.background = 'transparent' },
|
|
2002
|
+
style: {
|
|
2003
|
+
...SHOT_BUTTON_STYLE,
|
|
2004
|
+
color: phase === 'failed'
|
|
2005
|
+
? 'var(--dsw-alias-label-error, #b42318)'
|
|
2006
|
+
: phase === 'done'
|
|
2007
|
+
? 'var(--dsw-alias-label-primary, #111418)'
|
|
2008
|
+
: SHOT_BUTTON_STYLE.color,
|
|
2009
|
+
cursor: phase === 'busy' ? 'progress' : 'pointer',
|
|
1980
2010
|
},
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
?
|
|
1986
|
-
: React.createElement('
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
position: 'fixed',
|
|
1990
|
-
top: anchor.top + SHOT_ANCHOR_BUTTON + 6 + 'px',
|
|
1991
|
-
left: Math.max(12, anchor.left - 140) + 'px',
|
|
1992
|
-
maxWidth: '260px',
|
|
1993
|
-
padding: '3px 8px',
|
|
1994
|
-
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1995
|
-
background: 'Canvas',
|
|
1996
|
-
boxShadow: 'var(--dsw-elevation-prominent, 0 3px 8px rgba(0,0,0,0.12))',
|
|
1997
|
-
color: phase === 'failed' ? 'var(--dsw-alias-label-error, #b42318)' : 'var(--dsw-alias-label-secondary, #61666b)',
|
|
1998
|
-
fontSize: '12px',
|
|
1999
|
-
lineHeight: '18px',
|
|
2000
|
-
whiteSpace: 'nowrap',
|
|
2001
|
-
zIndex: 41,
|
|
2002
|
-
},
|
|
2003
|
-
}, note))
|
|
2011
|
+
}, glyph)
|
|
2012
|
+
const Tooltip = primitives === undefined ? undefined : primitives.Tooltip
|
|
2013
|
+
return ReactDOM.createPortal(
|
|
2014
|
+
Tooltip === undefined
|
|
2015
|
+
? button
|
|
2016
|
+
: React.createElement(Tooltip, { label: tip, side: 'bottom', delayMs: 500 }, button),
|
|
2017
|
+
host,
|
|
2018
|
+
)
|
|
2004
2019
|
}
|
|
2005
2020
|
|
|
2006
2021
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "AryaCode Polish: one install for the small things that make AryaCode feel finished. One Plugins row, one switch per quality-of-life feature.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|