@aryacode/dsh-aryacode-polish 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0] - 2026-10-28
4
+
5
+ ### Added
6
+ - **Drop a workspace on a folder header to file it there.** The title is rewritten to
7
+ Folder/Name (any previous prefix is replaced, not stacked) through the Host rename.
8
+ - **A workspace note shows as a card on hover**, not just as a tooltip title.
9
+
10
+
3
11
  ## [0.16.0] - 2026-10-28
4
12
 
5
13
  ### Added
@@ -475,6 +475,22 @@ const WS_MENU_HINT = {
475
475
  color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
476
476
  }
477
477
 
478
+ const WS_NOTE_CARD = {
479
+ position: 'fixed',
480
+ zIndex: 1250,
481
+ maxWidth: '260px',
482
+ padding: '6px 9px',
483
+ borderRadius: 'var(--dsw-radius-md, 8px)',
484
+ border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
485
+ background: 'Canvas',
486
+ color: 'var(--dsw-alias-label-secondary, #61666b)',
487
+ fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 1px)',
488
+ lineHeight: 1.35,
489
+ boxShadow: 'var(--dsw-elevation-prominent, 0 2px 8px rgba(0, 0, 0, 0.08))',
490
+ pointerEvents: 'none',
491
+ whiteSpace: 'pre-wrap',
492
+ }
493
+
478
494
  const WS_GLYPH = {
479
495
  flex: 'none',
480
496
  width: '16px',
@@ -616,6 +632,19 @@ const SWITCHER_HINT = {
616
632
  * written to the Host and renaming a workspace is enough to move it between
617
633
  * folders — the same idea as the reference plugin, without its "/" toggle.
618
634
  */
635
+ /**
636
+ * The title a workspace needs to sit under `folder`: its own name moved behind the
637
+ * folder prefix, with any previous prefix replaced rather than stacked.
638
+ */
639
+ export function fileUnder(label, folder) {
640
+ const name = String(label || '').trim()
641
+ const target = String(folder || '').trim()
642
+ if (!name || !target) return name
643
+ const cut = name.lastIndexOf('/')
644
+ const bare = (cut >= 0 ? name.slice(cut + 1) : name).trim()
645
+ return bare ? target + '/' + bare : target
646
+ }
647
+
619
648
  export function folderKey(workspace) {
620
649
  const label = workspaceLabel(workspace)
621
650
  const cut = label.lastIndexOf('/')
@@ -833,6 +862,7 @@ export function BetterWorkspace(props) {
833
862
  const [dropping, setDropping] = React.useState(null)
834
863
  const [look, setLook] = React.useState(() => readAppearance())
835
864
  const [noteDraft, setNoteDraft] = React.useState('')
865
+ const [card, setCard] = React.useState(null)
836
866
  const folded = React.useMemo(() => readFolded(), [])
837
867
  const [folders, setFolders] = React.useState(folded.folders)
838
868
  const [switcher, setSwitcher] = React.useState(null)
@@ -1215,6 +1245,13 @@ export function BetterWorkspace(props) {
1215
1245
  React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
1216
1246
  : null
1217
1247
 
1248
+ const noteCard = card
1249
+ ? ReactDOM.createPortal(React.createElement('div', {
1250
+ 'data-polish-note-card': 'true',
1251
+ style: Object.assign({}, WS_NOTE_CARD, { top: card.top, left: card.left }),
1252
+ }, card.text), document.body)
1253
+ : null
1254
+
1218
1255
  const paintMenu = menu && menu.kind === 'paint'
1219
1256
  ? (() => {
1220
1257
  const workspace = menu.workspace
@@ -1477,8 +1514,31 @@ export function BetterWorkspace(props) {
1477
1514
  role: 'group',
1478
1515
  'aria-label': folder,
1479
1516
  title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
1480
- style: WS_FOLDER_ROW,
1517
+ style: Object.assign({}, WS_FOLDER_ROW, {
1518
+ background: dropping === 'folder:' + folder
1519
+ ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
1520
+ : undefined,
1521
+ }),
1481
1522
  onClick: () => setFolders((prev) => Object.assign({}, prev, { [folder]: prev[folder] !== true })),
1523
+ onDragOver: (event) => {
1524
+ if (!drag || drag.kind !== 'workspace') return
1525
+ if (folderOf(rows.find((entry) => entry.workspace.workspaceId === drag.id)?.workspace) === folder) return
1526
+ event.preventDefault()
1527
+ setDropping('folder:' + folder)
1528
+ },
1529
+ onDragLeave: () => setDropping(null),
1530
+ onDrop: (event) => {
1531
+ event.preventDefault()
1532
+ setDropping(null)
1533
+ if (!drag || drag.kind !== 'workspace') return
1534
+ const moving = rows.find((entry) => entry.workspace.workspaceId === drag.id)
1535
+ if (!moving || folderOf(moving.workspace) === folder) return
1536
+ const title = fileUnder(workspaceLabel(moving.workspace), folder)
1537
+ Promise.resolve()
1538
+ .then(() => services.renameWorkspace(moving.workspace.workspaceId, title))
1539
+ .then(() => setNotice('Filed under ' + folder + '.'))
1540
+ .catch(() => setNotice('Could not file that workspace.'))
1541
+ },
1482
1542
  },
1483
1543
  React.createElement('span', { 'aria-hidden': 'true' }, folded ? '›' : '⌄'),
1484
1544
  React.createElement('span', { style: WS_TITLE }, folder),
@@ -1512,8 +1572,14 @@ export function BetterWorkspace(props) {
1512
1572
  onClick: () => toggleWorkspace(workspace.workspaceId),
1513
1573
  onContextMenu: (event) => openWorkspaceMenu(event, workspace),
1514
1574
  title: looks.note || undefined,
1515
- onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
1516
- onMouseLeave: () => setHover(null),
1575
+ onMouseEnter: (event) => {
1576
+ setHover('workspace:' + workspace.workspaceId)
1577
+ const note = appearanceOf(look, workspace.workspaceId).note
1578
+ if (!note) return
1579
+ const box = event.currentTarget.getBoundingClientRect()
1580
+ setCard({ text: note, top: box.bottom + 6, left: Math.max(8, box.left) })
1581
+ },
1582
+ onMouseLeave: () => { setHover(null); setCard(null) },
1517
1583
  onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
1518
1584
  onDragOver: (event) => {
1519
1585
  if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
@@ -1609,6 +1675,7 @@ export function BetterWorkspace(props) {
1609
1675
  sessionMenu,
1610
1676
  glyphMenu,
1611
1677
  noteMenu,
1678
+ noteCard,
1612
1679
  archivedMenu,
1613
1680
  paintMenu,
1614
1681
  workspaceMenu)
@@ -186,3 +186,18 @@ and the note surfaces as the row's tooltip, so it costs no vertical space in the
186
186
 
187
187
  Glyph validation is deliberately narrow (one to four characters): the field is a badge in a
188
188
  16 px slot, not a place to paste a sentence.
189
+
190
+ ## Filing by drag
191
+
192
+ Dropping a workspace on a folder header files it there: the title is rewritten to
193
+ `Folder/Name` through the Host's own rename, so the move is durable and the shipped
194
+ browser (when this switch is off) sees exactly the same structure. `fileUnder(label, folder)`
195
+ replaces any previous prefix instead of stacking it — dragging `A/B/Gamma` onto `C` gives
196
+ `C/Gamma`, not `C/A/B/Gamma` — and a workspace already inside the target folder is not a
197
+ drop target, so the gesture cannot rename something onto itself.
198
+
199
+ ## The note card
200
+
201
+ A workspace note shows as a small card under the row on hover rather than as a browser
202
+ tooltip: tooltips are slow to appear, unstyleable, and vanish when the pointer leaves. The
203
+ card is portalled, `pointer-events: none`, and only exists while a note does.
package/lib/client.js CHANGED
@@ -53,7 +53,7 @@ window.__ModuleLoader__.load({
53
53
  ]
54
54
  const ROW_ID = "polish"
55
55
  const ROW_KEY = "@aryacode/dsh-aryacode-polish" + '#' + ROW_ID
56
- const CLIENT_VERSION = "0.16.0"
56
+ const CLIENT_VERSION = "0.17.0"
57
57
 
58
58
  /* html2canvas 1.4.1 — MIT, vendored from vendor/html2canvas.min.js.
59
59
  * A UMD build, so it assigns itself to globalThis; face.js reads it lazily. */
@@ -676,6 +676,22 @@ window.__ModuleLoader__.load({
676
676
  color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
677
677
  }
678
678
 
679
+ const WS_NOTE_CARD = {
680
+ position: 'fixed',
681
+ zIndex: 1250,
682
+ maxWidth: '260px',
683
+ padding: '6px 9px',
684
+ borderRadius: 'var(--dsw-radius-md, 8px)',
685
+ border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
686
+ background: 'Canvas',
687
+ color: 'var(--dsw-alias-label-secondary, #61666b)',
688
+ fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 1px)',
689
+ lineHeight: 1.35,
690
+ boxShadow: 'var(--dsw-elevation-prominent, 0 2px 8px rgba(0, 0, 0, 0.08))',
691
+ pointerEvents: 'none',
692
+ whiteSpace: 'pre-wrap',
693
+ }
694
+
679
695
  const WS_GLYPH = {
680
696
  flex: 'none',
681
697
  width: '16px',
@@ -817,6 +833,19 @@ window.__ModuleLoader__.load({
817
833
  * written to the Host and renaming a workspace is enough to move it between
818
834
  * folders — the same idea as the reference plugin, without its "/" toggle.
819
835
  */
836
+ /**
837
+ * The title a workspace needs to sit under `folder`: its own name moved behind the
838
+ * folder prefix, with any previous prefix replaced rather than stacked.
839
+ */
840
+ function fileUnder(label, folder) {
841
+ const name = String(label || '').trim()
842
+ const target = String(folder || '').trim()
843
+ if (!name || !target) return name
844
+ const cut = name.lastIndexOf('/')
845
+ const bare = (cut >= 0 ? name.slice(cut + 1) : name).trim()
846
+ return bare ? target + '/' + bare : target
847
+ }
848
+
820
849
  function folderKey(workspace) {
821
850
  const label = workspaceLabel(workspace)
822
851
  const cut = label.lastIndexOf('/')
@@ -1034,6 +1063,7 @@ window.__ModuleLoader__.load({
1034
1063
  const [dropping, setDropping] = React.useState(null)
1035
1064
  const [look, setLook] = React.useState(() => readAppearance())
1036
1065
  const [noteDraft, setNoteDraft] = React.useState('')
1066
+ const [card, setCard] = React.useState(null)
1037
1067
  const folded = React.useMemo(() => readFolded(), [])
1038
1068
  const [folders, setFolders] = React.useState(folded.folders)
1039
1069
  const [switcher, setSwitcher] = React.useState(null)
@@ -1416,6 +1446,13 @@ window.__ModuleLoader__.load({
1416
1446
  React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
1417
1447
  : null
1418
1448
 
1449
+ const noteCard = card
1450
+ ? ReactDOM.createPortal(React.createElement('div', {
1451
+ 'data-polish-note-card': 'true',
1452
+ style: Object.assign({}, WS_NOTE_CARD, { top: card.top, left: card.left }),
1453
+ }, card.text), document.body)
1454
+ : null
1455
+
1419
1456
  const paintMenu = menu && menu.kind === 'paint'
1420
1457
  ? (() => {
1421
1458
  const workspace = menu.workspace
@@ -1678,8 +1715,31 @@ window.__ModuleLoader__.load({
1678
1715
  role: 'group',
1679
1716
  'aria-label': folder,
1680
1717
  title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
1681
- style: WS_FOLDER_ROW,
1718
+ style: Object.assign({}, WS_FOLDER_ROW, {
1719
+ background: dropping === 'folder:' + folder
1720
+ ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
1721
+ : undefined,
1722
+ }),
1682
1723
  onClick: () => setFolders((prev) => Object.assign({}, prev, { [folder]: prev[folder] !== true })),
1724
+ onDragOver: (event) => {
1725
+ if (!drag || drag.kind !== 'workspace') return
1726
+ if (folderOf(rows.find((entry) => entry.workspace.workspaceId === drag.id)?.workspace) === folder) return
1727
+ event.preventDefault()
1728
+ setDropping('folder:' + folder)
1729
+ },
1730
+ onDragLeave: () => setDropping(null),
1731
+ onDrop: (event) => {
1732
+ event.preventDefault()
1733
+ setDropping(null)
1734
+ if (!drag || drag.kind !== 'workspace') return
1735
+ const moving = rows.find((entry) => entry.workspace.workspaceId === drag.id)
1736
+ if (!moving || folderOf(moving.workspace) === folder) return
1737
+ const title = fileUnder(workspaceLabel(moving.workspace), folder)
1738
+ Promise.resolve()
1739
+ .then(() => services.renameWorkspace(moving.workspace.workspaceId, title))
1740
+ .then(() => setNotice('Filed under ' + folder + '.'))
1741
+ .catch(() => setNotice('Could not file that workspace.'))
1742
+ },
1683
1743
  },
1684
1744
  React.createElement('span', { 'aria-hidden': 'true' }, folded ? '›' : '⌄'),
1685
1745
  React.createElement('span', { style: WS_TITLE }, folder),
@@ -1713,8 +1773,14 @@ window.__ModuleLoader__.load({
1713
1773
  onClick: () => toggleWorkspace(workspace.workspaceId),
1714
1774
  onContextMenu: (event) => openWorkspaceMenu(event, workspace),
1715
1775
  title: looks.note || undefined,
1716
- onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
1717
- onMouseLeave: () => setHover(null),
1776
+ onMouseEnter: (event) => {
1777
+ setHover('workspace:' + workspace.workspaceId)
1778
+ const note = appearanceOf(look, workspace.workspaceId).note
1779
+ if (!note) return
1780
+ const box = event.currentTarget.getBoundingClientRect()
1781
+ setCard({ text: note, top: box.bottom + 6, left: Math.max(8, box.left) })
1782
+ },
1783
+ onMouseLeave: () => { setHover(null); setCard(null) },
1718
1784
  onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
1719
1785
  onDragOver: (event) => {
1720
1786
  if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
@@ -1810,6 +1876,7 @@ window.__ModuleLoader__.load({
1810
1876
  sessionMenu,
1811
1877
  glyphMenu,
1812
1878
  noteMenu,
1879
+ noteCard,
1813
1880
  archivedMenu,
1814
1881
  paintMenu,
1815
1882
  workspaceMenu)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryacode/dsh-aryacode-polish",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
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",