@aryacode/dsh-aryacode-polish 0.15.2 โ†’ 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,22 @@
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
+
11
+ ## [0.16.0] - 2026-10-28
12
+
13
+ ### Added
14
+ - **A glyph per workspace.** The workspace menu can put an emoji badge in front of a
15
+ workspace instead of the folder icon, and the choice is remembered with the colour.
16
+ - **A note per workspace.** The note shows as the row tooltip and is edited from the same
17
+ menu; Enter saves, Escape cancels.
18
+
19
+
3
20
  ## [0.15.2] - 2026-10-28
4
21
 
5
22
  ### Added
@@ -435,6 +435,70 @@ export function splitArchived(sessions, archivedIds) {
435
435
  return { active, archived }
436
436
  }
437
437
 
438
+ const WS_GLYPH_GRID = {
439
+ display: 'grid',
440
+ gridTemplateColumns: 'repeat(6, 26px)',
441
+ gap: '2px',
442
+ padding: '2px',
443
+ }
444
+
445
+ const WS_GLYPH_BUTTON = {
446
+ width: '26px',
447
+ height: '26px',
448
+ display: 'inline-flex',
449
+ alignItems: 'center',
450
+ justifyContent: 'center',
451
+ padding: 0,
452
+ border: 'none',
453
+ borderRadius: 'var(--dsw-radius-sm, 6px)',
454
+ background: 'transparent',
455
+ fontSize: '15px',
456
+ lineHeight: 1,
457
+ cursor: 'pointer',
458
+ }
459
+
460
+ const WS_NOTE_INPUT = {
461
+ width: '220px',
462
+ boxSizing: 'border-box',
463
+ padding: '5px 8px',
464
+ border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
465
+ borderRadius: 'var(--dsw-radius-sm, 6px)',
466
+ background: 'transparent',
467
+ color: 'inherit',
468
+ font: 'inherit',
469
+ outline: 'none',
470
+ }
471
+
472
+ const WS_MENU_HINT = {
473
+ padding: '4px 8px 2px',
474
+ fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
475
+ color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
476
+ }
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
+
494
+ const WS_GLYPH = {
495
+ flex: 'none',
496
+ width: '16px',
497
+ textAlign: 'center',
498
+ fontSize: '13px',
499
+ lineHeight: 1,
500
+ }
501
+
438
502
  const WS_SECTION = {
439
503
  display: 'flex',
440
504
  alignItems: 'center',
@@ -568,6 +632,19 @@ const SWITCHER_HINT = {
568
632
  * written to the Host and renaming a workspace is enough to move it between
569
633
  * folders โ€” the same idea as the reference plugin, without its "/" toggle.
570
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
+
571
648
  export function folderKey(workspace) {
572
649
  const label = workspaceLabel(workspace)
573
650
  const cut = label.lastIndexOf('/')
@@ -645,19 +722,29 @@ export const TONE_COLOR = {
645
722
  violet: '#7a5cc4',
646
723
  }
647
724
 
725
+ /** Glyphs a workspace can wear instead of the folder icon. */
726
+ export const GLYPHS = ['๐Ÿ“', '๐Ÿš€', '๐Ÿงช', '๐Ÿ', 'โš™๏ธ', '๐Ÿ“ฆ', '๐Ÿงญ', 'โœจ', '๐Ÿง ', '๐Ÿ—‚๏ธ']
727
+
648
728
  export function appearanceOf(map, workspaceId) {
649
729
  const entry = (map && map[workspaceId]) || {}
650
730
  return {
651
731
  tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
652
732
  strong: entry.strong === true,
733
+ glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
734
+ note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
653
735
  }
654
736
  }
655
737
 
656
738
  export function withAppearance(map, workspaceId, patch) {
657
739
  const next = Object.assign({}, map || {})
658
740
  const merged = Object.assign({}, appearanceOf(next, workspaceId), patch || {})
659
- if (!merged.tone && merged.strong !== true) delete next[workspaceId]
660
- else next[workspaceId] = { tone: merged.tone, strong: merged.strong === true }
741
+ const entry = {}
742
+ if (merged.tone) entry.tone = merged.tone
743
+ if (merged.strong === true) entry.strong = true
744
+ if (merged.glyph) entry.glyph = merged.glyph
745
+ if (merged.note) entry.note = merged.note
746
+ if (Object.keys(entry).length === 0) delete next[workspaceId]
747
+ else next[workspaceId] = entry
661
748
  return next
662
749
  }
663
750
 
@@ -774,6 +861,8 @@ export function BetterWorkspace(props) {
774
861
  const [drag, setDrag] = React.useState(null)
775
862
  const [dropping, setDropping] = React.useState(null)
776
863
  const [look, setLook] = React.useState(() => readAppearance())
864
+ const [noteDraft, setNoteDraft] = React.useState('')
865
+ const [card, setCard] = React.useState(null)
777
866
  const folded = React.useMemo(() => readFolded(), [])
778
867
  const [folders, setFolders] = React.useState(folded.folders)
779
868
  const [switcher, setSwitcher] = React.useState(null)
@@ -1035,6 +1124,29 @@ export function BetterWorkspace(props) {
1035
1124
  }
1036
1125
  },
1037
1126
  }, 'Restore saved order'),
1127
+ React.createElement('button', {
1128
+ type: 'button',
1129
+ 'data-polish-glyph': 'open',
1130
+ style: WS_MENU_ITEM,
1131
+ onClick: () => {
1132
+ const target = menu.workspace
1133
+ const anchor = menu.anchor
1134
+ closeMenu()
1135
+ setMenu({ kind: 'glyph', workspace: target, anchor })
1136
+ },
1137
+ }, 'Glyphโ€ฆ'),
1138
+ React.createElement('button', {
1139
+ type: 'button',
1140
+ 'data-polish-note': 'open',
1141
+ style: WS_MENU_ITEM,
1142
+ onClick: () => {
1143
+ const target = menu.workspace
1144
+ const anchor = menu.anchor
1145
+ closeMenu()
1146
+ setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
1147
+ setMenu({ kind: 'note', workspace: target, anchor })
1148
+ },
1149
+ }, 'Noteโ€ฆ'),
1038
1150
  React.createElement('button', {
1039
1151
  type: 'button',
1040
1152
  'data-polish-ws-appearance': 'open',
@@ -1079,6 +1191,67 @@ export function BetterWorkspace(props) {
1079
1191
  }, 'Unarchive session'))
1080
1192
  : null
1081
1193
 
1194
+ const glyphMenu = menu && menu.kind === 'glyph'
1195
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1196
+ React.createElement('div', { style: WS_GLYPH_GRID },
1197
+ GLYPHS.map((glyph) => React.createElement('button', {
1198
+ key: 'glyph:' + glyph,
1199
+ type: 'button',
1200
+ 'data-polish-glyph-pick': glyph,
1201
+ title: 'Use ' + glyph,
1202
+ style: Object.assign({}, WS_GLYPH_BUTTON, {
1203
+ background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
1204
+ ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
1205
+ : undefined,
1206
+ }),
1207
+ onClick: () => {
1208
+ const target = menu.workspace
1209
+ closeMenu()
1210
+ paintLook(target.workspaceId, { glyph })
1211
+ },
1212
+ }, glyph)),
1213
+ React.createElement('button', {
1214
+ key: 'glyph:none',
1215
+ type: 'button',
1216
+ 'data-polish-glyph-pick': 'none',
1217
+ style: WS_GLYPH_BUTTON,
1218
+ onClick: () => {
1219
+ const target = menu.workspace
1220
+ closeMenu()
1221
+ paintLook(target.workspaceId, { glyph: '' })
1222
+ },
1223
+ }, 'โŒซ')))
1224
+ : null
1225
+
1226
+ const noteMenu = menu && menu.kind === 'note'
1227
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1228
+ React.createElement('input', {
1229
+ 'data-polish-note-input': 'true',
1230
+ type: 'text',
1231
+ value: noteDraft,
1232
+ placeholder: 'A note for this workspace',
1233
+ autoFocus: true,
1234
+ style: WS_NOTE_INPUT,
1235
+ onChange: (event) => setNoteDraft(event.target.value),
1236
+ onKeyDown: (event) => {
1237
+ if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
1238
+ if (event.key !== 'Enter') return
1239
+ const target = menu.workspace
1240
+ const value = noteDraft.trim()
1241
+ closeMenu()
1242
+ paintLook(target.workspaceId, { note: value })
1243
+ },
1244
+ }),
1245
+ React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
1246
+ : null
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
+
1082
1255
  const paintMenu = menu && menu.kind === 'paint'
1083
1256
  ? (() => {
1084
1257
  const workspace = menu.workspace
@@ -1341,8 +1514,31 @@ export function BetterWorkspace(props) {
1341
1514
  role: 'group',
1342
1515
  'aria-label': folder,
1343
1516
  title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
1344
- 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
+ }),
1345
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
+ },
1346
1542
  },
1347
1543
  React.createElement('span', { 'aria-hidden': 'true' }, folded ? 'โ€บ' : 'โŒ„'),
1348
1544
  React.createElement('span', { style: WS_TITLE }, folder),
@@ -1375,8 +1571,15 @@ export function BetterWorkspace(props) {
1375
1571
  }),
1376
1572
  onClick: () => toggleWorkspace(workspace.workspaceId),
1377
1573
  onContextMenu: (event) => openWorkspaceMenu(event, workspace),
1378
- onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
1379
- onMouseLeave: () => setHover(null),
1574
+ title: looks.note || undefined,
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) },
1380
1583
  onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
1381
1584
  onDragOver: (event) => {
1382
1585
  if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
@@ -1387,7 +1590,9 @@ export function BetterWorkspace(props) {
1387
1590
  onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
1388
1591
  },
1389
1592
  React.createElement(CaretGlyph, { open }),
1390
- React.createElement(FolderGlyph),
1593
+ looks.glyph
1594
+ ? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
1595
+ : React.createElement(FolderGlyph),
1391
1596
  looks.tone
1392
1597
  ? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
1393
1598
  : null,
@@ -1468,6 +1673,9 @@ export function BetterWorkspace(props) {
1468
1673
  : null,
1469
1674
  switcherCard,
1470
1675
  sessionMenu,
1676
+ glyphMenu,
1677
+ noteMenu,
1678
+ noteCard,
1471
1679
  archivedMenu,
1472
1680
  paintMenu,
1473
1681
  workspaceMenu)
@@ -176,3 +176,28 @@ were being rendered inline in their workspace as if they were live.
176
176
  The header's fold control expands when anything is folded (folders included) and folds
177
177
  only when nothing is. An earlier version only looked at workspace rows, so a folded
178
178
  folder left the button saying "Expand all" while its workspaces stayed hidden.
179
+
180
+ ## Glyphs and notes
181
+
182
+ A workspace can wear an emoji badge instead of the folder icon (**Glyphโ€ฆ** in its menu, ten
183
+ badges plus a clear button) and carry a short note (**Noteโ€ฆ**, Enter saves, Escape cancels).
184
+ Both live in the same appearance record as the colour โ€” one localStorage key, one shape โ€”
185
+ and the note surfaces as the row's tooltip, so it costs no vertical space in the tree.
186
+
187
+ Glyph validation is deliberately narrow (one to four characters): the field is a badge in a
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.15.2"
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. */
@@ -636,6 +636,70 @@ window.__ModuleLoader__.load({
636
636
  return { active, archived }
637
637
  }
638
638
 
639
+ const WS_GLYPH_GRID = {
640
+ display: 'grid',
641
+ gridTemplateColumns: 'repeat(6, 26px)',
642
+ gap: '2px',
643
+ padding: '2px',
644
+ }
645
+
646
+ const WS_GLYPH_BUTTON = {
647
+ width: '26px',
648
+ height: '26px',
649
+ display: 'inline-flex',
650
+ alignItems: 'center',
651
+ justifyContent: 'center',
652
+ padding: 0,
653
+ border: 'none',
654
+ borderRadius: 'var(--dsw-radius-sm, 6px)',
655
+ background: 'transparent',
656
+ fontSize: '15px',
657
+ lineHeight: 1,
658
+ cursor: 'pointer',
659
+ }
660
+
661
+ const WS_NOTE_INPUT = {
662
+ width: '220px',
663
+ boxSizing: 'border-box',
664
+ padding: '5px 8px',
665
+ border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
666
+ borderRadius: 'var(--dsw-radius-sm, 6px)',
667
+ background: 'transparent',
668
+ color: 'inherit',
669
+ font: 'inherit',
670
+ outline: 'none',
671
+ }
672
+
673
+ const WS_MENU_HINT = {
674
+ padding: '4px 8px 2px',
675
+ fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
676
+ color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
677
+ }
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
+
695
+ const WS_GLYPH = {
696
+ flex: 'none',
697
+ width: '16px',
698
+ textAlign: 'center',
699
+ fontSize: '13px',
700
+ lineHeight: 1,
701
+ }
702
+
639
703
  const WS_SECTION = {
640
704
  display: 'flex',
641
705
  alignItems: 'center',
@@ -769,6 +833,19 @@ window.__ModuleLoader__.load({
769
833
  * written to the Host and renaming a workspace is enough to move it between
770
834
  * folders โ€” the same idea as the reference plugin, without its "/" toggle.
771
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
+
772
849
  function folderKey(workspace) {
773
850
  const label = workspaceLabel(workspace)
774
851
  const cut = label.lastIndexOf('/')
@@ -846,19 +923,29 @@ window.__ModuleLoader__.load({
846
923
  violet: '#7a5cc4',
847
924
  }
848
925
 
926
+ /** Glyphs a workspace can wear instead of the folder icon. */
927
+ const GLYPHS = ['๐Ÿ“', '๐Ÿš€', '๐Ÿงช', '๐Ÿ', 'โš™๏ธ', '๐Ÿ“ฆ', '๐Ÿงญ', 'โœจ', '๐Ÿง ', '๐Ÿ—‚๏ธ']
928
+
849
929
  function appearanceOf(map, workspaceId) {
850
930
  const entry = (map && map[workspaceId]) || {}
851
931
  return {
852
932
  tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
853
933
  strong: entry.strong === true,
934
+ glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
935
+ note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
854
936
  }
855
937
  }
856
938
 
857
939
  function withAppearance(map, workspaceId, patch) {
858
940
  const next = Object.assign({}, map || {})
859
941
  const merged = Object.assign({}, appearanceOf(next, workspaceId), patch || {})
860
- if (!merged.tone && merged.strong !== true) delete next[workspaceId]
861
- else next[workspaceId] = { tone: merged.tone, strong: merged.strong === true }
942
+ const entry = {}
943
+ if (merged.tone) entry.tone = merged.tone
944
+ if (merged.strong === true) entry.strong = true
945
+ if (merged.glyph) entry.glyph = merged.glyph
946
+ if (merged.note) entry.note = merged.note
947
+ if (Object.keys(entry).length === 0) delete next[workspaceId]
948
+ else next[workspaceId] = entry
862
949
  return next
863
950
  }
864
951
 
@@ -975,6 +1062,8 @@ window.__ModuleLoader__.load({
975
1062
  const [drag, setDrag] = React.useState(null)
976
1063
  const [dropping, setDropping] = React.useState(null)
977
1064
  const [look, setLook] = React.useState(() => readAppearance())
1065
+ const [noteDraft, setNoteDraft] = React.useState('')
1066
+ const [card, setCard] = React.useState(null)
978
1067
  const folded = React.useMemo(() => readFolded(), [])
979
1068
  const [folders, setFolders] = React.useState(folded.folders)
980
1069
  const [switcher, setSwitcher] = React.useState(null)
@@ -1236,6 +1325,29 @@ window.__ModuleLoader__.load({
1236
1325
  }
1237
1326
  },
1238
1327
  }, 'Restore saved order'),
1328
+ React.createElement('button', {
1329
+ type: 'button',
1330
+ 'data-polish-glyph': 'open',
1331
+ style: WS_MENU_ITEM,
1332
+ onClick: () => {
1333
+ const target = menu.workspace
1334
+ const anchor = menu.anchor
1335
+ closeMenu()
1336
+ setMenu({ kind: 'glyph', workspace: target, anchor })
1337
+ },
1338
+ }, 'Glyphโ€ฆ'),
1339
+ React.createElement('button', {
1340
+ type: 'button',
1341
+ 'data-polish-note': 'open',
1342
+ style: WS_MENU_ITEM,
1343
+ onClick: () => {
1344
+ const target = menu.workspace
1345
+ const anchor = menu.anchor
1346
+ closeMenu()
1347
+ setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
1348
+ setMenu({ kind: 'note', workspace: target, anchor })
1349
+ },
1350
+ }, 'Noteโ€ฆ'),
1239
1351
  React.createElement('button', {
1240
1352
  type: 'button',
1241
1353
  'data-polish-ws-appearance': 'open',
@@ -1280,6 +1392,67 @@ window.__ModuleLoader__.load({
1280
1392
  }, 'Unarchive session'))
1281
1393
  : null
1282
1394
 
1395
+ const glyphMenu = menu && menu.kind === 'glyph'
1396
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1397
+ React.createElement('div', { style: WS_GLYPH_GRID },
1398
+ GLYPHS.map((glyph) => React.createElement('button', {
1399
+ key: 'glyph:' + glyph,
1400
+ type: 'button',
1401
+ 'data-polish-glyph-pick': glyph,
1402
+ title: 'Use ' + glyph,
1403
+ style: Object.assign({}, WS_GLYPH_BUTTON, {
1404
+ background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
1405
+ ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
1406
+ : undefined,
1407
+ }),
1408
+ onClick: () => {
1409
+ const target = menu.workspace
1410
+ closeMenu()
1411
+ paintLook(target.workspaceId, { glyph })
1412
+ },
1413
+ }, glyph)),
1414
+ React.createElement('button', {
1415
+ key: 'glyph:none',
1416
+ type: 'button',
1417
+ 'data-polish-glyph-pick': 'none',
1418
+ style: WS_GLYPH_BUTTON,
1419
+ onClick: () => {
1420
+ const target = menu.workspace
1421
+ closeMenu()
1422
+ paintLook(target.workspaceId, { glyph: '' })
1423
+ },
1424
+ }, 'โŒซ')))
1425
+ : null
1426
+
1427
+ const noteMenu = menu && menu.kind === 'note'
1428
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1429
+ React.createElement('input', {
1430
+ 'data-polish-note-input': 'true',
1431
+ type: 'text',
1432
+ value: noteDraft,
1433
+ placeholder: 'A note for this workspace',
1434
+ autoFocus: true,
1435
+ style: WS_NOTE_INPUT,
1436
+ onChange: (event) => setNoteDraft(event.target.value),
1437
+ onKeyDown: (event) => {
1438
+ if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
1439
+ if (event.key !== 'Enter') return
1440
+ const target = menu.workspace
1441
+ const value = noteDraft.trim()
1442
+ closeMenu()
1443
+ paintLook(target.workspaceId, { note: value })
1444
+ },
1445
+ }),
1446
+ React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
1447
+ : null
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
+
1283
1456
  const paintMenu = menu && menu.kind === 'paint'
1284
1457
  ? (() => {
1285
1458
  const workspace = menu.workspace
@@ -1542,8 +1715,31 @@ window.__ModuleLoader__.load({
1542
1715
  role: 'group',
1543
1716
  'aria-label': folder,
1544
1717
  title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
1545
- 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
+ }),
1546
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
+ },
1547
1743
  },
1548
1744
  React.createElement('span', { 'aria-hidden': 'true' }, folded ? 'โ€บ' : 'โŒ„'),
1549
1745
  React.createElement('span', { style: WS_TITLE }, folder),
@@ -1576,8 +1772,15 @@ window.__ModuleLoader__.load({
1576
1772
  }),
1577
1773
  onClick: () => toggleWorkspace(workspace.workspaceId),
1578
1774
  onContextMenu: (event) => openWorkspaceMenu(event, workspace),
1579
- onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
1580
- onMouseLeave: () => setHover(null),
1775
+ title: looks.note || undefined,
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) },
1581
1784
  onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
1582
1785
  onDragOver: (event) => {
1583
1786
  if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
@@ -1588,7 +1791,9 @@ window.__ModuleLoader__.load({
1588
1791
  onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
1589
1792
  },
1590
1793
  React.createElement(CaretGlyph, { open }),
1591
- React.createElement(FolderGlyph),
1794
+ looks.glyph
1795
+ ? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
1796
+ : React.createElement(FolderGlyph),
1592
1797
  looks.tone
1593
1798
  ? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
1594
1799
  : null,
@@ -1669,6 +1874,9 @@ window.__ModuleLoader__.load({
1669
1874
  : null,
1670
1875
  switcherCard,
1671
1876
  sessionMenu,
1877
+ glyphMenu,
1878
+ noteMenu,
1879
+ noteCard,
1672
1880
  archivedMenu,
1673
1881
  paintMenu,
1674
1882
  workspaceMenu)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryacode/dsh-aryacode-polish",
3
- "version": "0.15.2",
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",