@aryacode/dsh-aryacode-polish 0.15.2 → 0.16.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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0] - 2026-10-28
4
+
5
+ ### Added
6
+ - **A glyph per workspace.** The workspace menu can put an emoji badge in front of a
7
+ workspace instead of the folder icon, and the choice is remembered with the colour.
8
+ - **A note per workspace.** The note shows as the row tooltip and is edited from the same
9
+ menu; Enter saves, Escape cancels.
10
+
11
+
3
12
  ## [0.15.2] - 2026-10-28
4
13
 
5
14
  ### Added
@@ -435,6 +435,54 @@ 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_GLYPH = {
479
+ flex: 'none',
480
+ width: '16px',
481
+ textAlign: 'center',
482
+ fontSize: '13px',
483
+ lineHeight: 1,
484
+ }
485
+
438
486
  const WS_SECTION = {
439
487
  display: 'flex',
440
488
  alignItems: 'center',
@@ -645,19 +693,29 @@ export const TONE_COLOR = {
645
693
  violet: '#7a5cc4',
646
694
  }
647
695
 
696
+ /** Glyphs a workspace can wear instead of the folder icon. */
697
+ export const GLYPHS = ['📁', '🚀', '🧪', '🐍', '⚙️', '📦', '🧭', '✨', '🧠', '🗂️']
698
+
648
699
  export function appearanceOf(map, workspaceId) {
649
700
  const entry = (map && map[workspaceId]) || {}
650
701
  return {
651
702
  tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
652
703
  strong: entry.strong === true,
704
+ glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
705
+ note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
653
706
  }
654
707
  }
655
708
 
656
709
  export function withAppearance(map, workspaceId, patch) {
657
710
  const next = Object.assign({}, map || {})
658
711
  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 }
712
+ const entry = {}
713
+ if (merged.tone) entry.tone = merged.tone
714
+ if (merged.strong === true) entry.strong = true
715
+ if (merged.glyph) entry.glyph = merged.glyph
716
+ if (merged.note) entry.note = merged.note
717
+ if (Object.keys(entry).length === 0) delete next[workspaceId]
718
+ else next[workspaceId] = entry
661
719
  return next
662
720
  }
663
721
 
@@ -774,6 +832,7 @@ export function BetterWorkspace(props) {
774
832
  const [drag, setDrag] = React.useState(null)
775
833
  const [dropping, setDropping] = React.useState(null)
776
834
  const [look, setLook] = React.useState(() => readAppearance())
835
+ const [noteDraft, setNoteDraft] = React.useState('')
777
836
  const folded = React.useMemo(() => readFolded(), [])
778
837
  const [folders, setFolders] = React.useState(folded.folders)
779
838
  const [switcher, setSwitcher] = React.useState(null)
@@ -1035,6 +1094,29 @@ export function BetterWorkspace(props) {
1035
1094
  }
1036
1095
  },
1037
1096
  }, 'Restore saved order'),
1097
+ React.createElement('button', {
1098
+ type: 'button',
1099
+ 'data-polish-glyph': 'open',
1100
+ style: WS_MENU_ITEM,
1101
+ onClick: () => {
1102
+ const target = menu.workspace
1103
+ const anchor = menu.anchor
1104
+ closeMenu()
1105
+ setMenu({ kind: 'glyph', workspace: target, anchor })
1106
+ },
1107
+ }, 'Glyph…'),
1108
+ React.createElement('button', {
1109
+ type: 'button',
1110
+ 'data-polish-note': 'open',
1111
+ style: WS_MENU_ITEM,
1112
+ onClick: () => {
1113
+ const target = menu.workspace
1114
+ const anchor = menu.anchor
1115
+ closeMenu()
1116
+ setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
1117
+ setMenu({ kind: 'note', workspace: target, anchor })
1118
+ },
1119
+ }, 'Note…'),
1038
1120
  React.createElement('button', {
1039
1121
  type: 'button',
1040
1122
  'data-polish-ws-appearance': 'open',
@@ -1079,6 +1161,60 @@ export function BetterWorkspace(props) {
1079
1161
  }, 'Unarchive session'))
1080
1162
  : null
1081
1163
 
1164
+ const glyphMenu = menu && menu.kind === 'glyph'
1165
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1166
+ React.createElement('div', { style: WS_GLYPH_GRID },
1167
+ GLYPHS.map((glyph) => React.createElement('button', {
1168
+ key: 'glyph:' + glyph,
1169
+ type: 'button',
1170
+ 'data-polish-glyph-pick': glyph,
1171
+ title: 'Use ' + glyph,
1172
+ style: Object.assign({}, WS_GLYPH_BUTTON, {
1173
+ background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
1174
+ ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
1175
+ : undefined,
1176
+ }),
1177
+ onClick: () => {
1178
+ const target = menu.workspace
1179
+ closeMenu()
1180
+ paintLook(target.workspaceId, { glyph })
1181
+ },
1182
+ }, glyph)),
1183
+ React.createElement('button', {
1184
+ key: 'glyph:none',
1185
+ type: 'button',
1186
+ 'data-polish-glyph-pick': 'none',
1187
+ style: WS_GLYPH_BUTTON,
1188
+ onClick: () => {
1189
+ const target = menu.workspace
1190
+ closeMenu()
1191
+ paintLook(target.workspaceId, { glyph: '' })
1192
+ },
1193
+ }, '⌫')))
1194
+ : null
1195
+
1196
+ const noteMenu = menu && menu.kind === 'note'
1197
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1198
+ React.createElement('input', {
1199
+ 'data-polish-note-input': 'true',
1200
+ type: 'text',
1201
+ value: noteDraft,
1202
+ placeholder: 'A note for this workspace',
1203
+ autoFocus: true,
1204
+ style: WS_NOTE_INPUT,
1205
+ onChange: (event) => setNoteDraft(event.target.value),
1206
+ onKeyDown: (event) => {
1207
+ if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
1208
+ if (event.key !== 'Enter') return
1209
+ const target = menu.workspace
1210
+ const value = noteDraft.trim()
1211
+ closeMenu()
1212
+ paintLook(target.workspaceId, { note: value })
1213
+ },
1214
+ }),
1215
+ React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
1216
+ : null
1217
+
1082
1218
  const paintMenu = menu && menu.kind === 'paint'
1083
1219
  ? (() => {
1084
1220
  const workspace = menu.workspace
@@ -1375,6 +1511,7 @@ export function BetterWorkspace(props) {
1375
1511
  }),
1376
1512
  onClick: () => toggleWorkspace(workspace.workspaceId),
1377
1513
  onContextMenu: (event) => openWorkspaceMenu(event, workspace),
1514
+ title: looks.note || undefined,
1378
1515
  onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
1379
1516
  onMouseLeave: () => setHover(null),
1380
1517
  onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
@@ -1387,7 +1524,9 @@ export function BetterWorkspace(props) {
1387
1524
  onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
1388
1525
  },
1389
1526
  React.createElement(CaretGlyph, { open }),
1390
- React.createElement(FolderGlyph),
1527
+ looks.glyph
1528
+ ? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
1529
+ : React.createElement(FolderGlyph),
1391
1530
  looks.tone
1392
1531
  ? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
1393
1532
  : null,
@@ -1468,6 +1607,8 @@ export function BetterWorkspace(props) {
1468
1607
  : null,
1469
1608
  switcherCard,
1470
1609
  sessionMenu,
1610
+ glyphMenu,
1611
+ noteMenu,
1471
1612
  archivedMenu,
1472
1613
  paintMenu,
1473
1614
  workspaceMenu)
@@ -176,3 +176,13 @@ 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.
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.16.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,54 @@ 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_GLYPH = {
680
+ flex: 'none',
681
+ width: '16px',
682
+ textAlign: 'center',
683
+ fontSize: '13px',
684
+ lineHeight: 1,
685
+ }
686
+
639
687
  const WS_SECTION = {
640
688
  display: 'flex',
641
689
  alignItems: 'center',
@@ -846,19 +894,29 @@ window.__ModuleLoader__.load({
846
894
  violet: '#7a5cc4',
847
895
  }
848
896
 
897
+ /** Glyphs a workspace can wear instead of the folder icon. */
898
+ const GLYPHS = ['📁', '🚀', '🧪', '🐍', '⚙️', '📦', '🧭', '✨', '🧠', '🗂️']
899
+
849
900
  function appearanceOf(map, workspaceId) {
850
901
  const entry = (map && map[workspaceId]) || {}
851
902
  return {
852
903
  tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
853
904
  strong: entry.strong === true,
905
+ glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
906
+ note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
854
907
  }
855
908
  }
856
909
 
857
910
  function withAppearance(map, workspaceId, patch) {
858
911
  const next = Object.assign({}, map || {})
859
912
  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 }
913
+ const entry = {}
914
+ if (merged.tone) entry.tone = merged.tone
915
+ if (merged.strong === true) entry.strong = true
916
+ if (merged.glyph) entry.glyph = merged.glyph
917
+ if (merged.note) entry.note = merged.note
918
+ if (Object.keys(entry).length === 0) delete next[workspaceId]
919
+ else next[workspaceId] = entry
862
920
  return next
863
921
  }
864
922
 
@@ -975,6 +1033,7 @@ window.__ModuleLoader__.load({
975
1033
  const [drag, setDrag] = React.useState(null)
976
1034
  const [dropping, setDropping] = React.useState(null)
977
1035
  const [look, setLook] = React.useState(() => readAppearance())
1036
+ const [noteDraft, setNoteDraft] = React.useState('')
978
1037
  const folded = React.useMemo(() => readFolded(), [])
979
1038
  const [folders, setFolders] = React.useState(folded.folders)
980
1039
  const [switcher, setSwitcher] = React.useState(null)
@@ -1236,6 +1295,29 @@ window.__ModuleLoader__.load({
1236
1295
  }
1237
1296
  },
1238
1297
  }, 'Restore saved order'),
1298
+ React.createElement('button', {
1299
+ type: 'button',
1300
+ 'data-polish-glyph': 'open',
1301
+ style: WS_MENU_ITEM,
1302
+ onClick: () => {
1303
+ const target = menu.workspace
1304
+ const anchor = menu.anchor
1305
+ closeMenu()
1306
+ setMenu({ kind: 'glyph', workspace: target, anchor })
1307
+ },
1308
+ }, 'Glyph…'),
1309
+ React.createElement('button', {
1310
+ type: 'button',
1311
+ 'data-polish-note': 'open',
1312
+ style: WS_MENU_ITEM,
1313
+ onClick: () => {
1314
+ const target = menu.workspace
1315
+ const anchor = menu.anchor
1316
+ closeMenu()
1317
+ setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
1318
+ setMenu({ kind: 'note', workspace: target, anchor })
1319
+ },
1320
+ }, 'Note…'),
1239
1321
  React.createElement('button', {
1240
1322
  type: 'button',
1241
1323
  'data-polish-ws-appearance': 'open',
@@ -1280,6 +1362,60 @@ window.__ModuleLoader__.load({
1280
1362
  }, 'Unarchive session'))
1281
1363
  : null
1282
1364
 
1365
+ const glyphMenu = menu && menu.kind === 'glyph'
1366
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1367
+ React.createElement('div', { style: WS_GLYPH_GRID },
1368
+ GLYPHS.map((glyph) => React.createElement('button', {
1369
+ key: 'glyph:' + glyph,
1370
+ type: 'button',
1371
+ 'data-polish-glyph-pick': glyph,
1372
+ title: 'Use ' + glyph,
1373
+ style: Object.assign({}, WS_GLYPH_BUTTON, {
1374
+ background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
1375
+ ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
1376
+ : undefined,
1377
+ }),
1378
+ onClick: () => {
1379
+ const target = menu.workspace
1380
+ closeMenu()
1381
+ paintLook(target.workspaceId, { glyph })
1382
+ },
1383
+ }, glyph)),
1384
+ React.createElement('button', {
1385
+ key: 'glyph:none',
1386
+ type: 'button',
1387
+ 'data-polish-glyph-pick': 'none',
1388
+ style: WS_GLYPH_BUTTON,
1389
+ onClick: () => {
1390
+ const target = menu.workspace
1391
+ closeMenu()
1392
+ paintLook(target.workspaceId, { glyph: '' })
1393
+ },
1394
+ }, '⌫')))
1395
+ : null
1396
+
1397
+ const noteMenu = menu && menu.kind === 'note'
1398
+ ? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
1399
+ React.createElement('input', {
1400
+ 'data-polish-note-input': 'true',
1401
+ type: 'text',
1402
+ value: noteDraft,
1403
+ placeholder: 'A note for this workspace',
1404
+ autoFocus: true,
1405
+ style: WS_NOTE_INPUT,
1406
+ onChange: (event) => setNoteDraft(event.target.value),
1407
+ onKeyDown: (event) => {
1408
+ if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
1409
+ if (event.key !== 'Enter') return
1410
+ const target = menu.workspace
1411
+ const value = noteDraft.trim()
1412
+ closeMenu()
1413
+ paintLook(target.workspaceId, { note: value })
1414
+ },
1415
+ }),
1416
+ React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
1417
+ : null
1418
+
1283
1419
  const paintMenu = menu && menu.kind === 'paint'
1284
1420
  ? (() => {
1285
1421
  const workspace = menu.workspace
@@ -1576,6 +1712,7 @@ window.__ModuleLoader__.load({
1576
1712
  }),
1577
1713
  onClick: () => toggleWorkspace(workspace.workspaceId),
1578
1714
  onContextMenu: (event) => openWorkspaceMenu(event, workspace),
1715
+ title: looks.note || undefined,
1579
1716
  onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
1580
1717
  onMouseLeave: () => setHover(null),
1581
1718
  onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
@@ -1588,7 +1725,9 @@ window.__ModuleLoader__.load({
1588
1725
  onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
1589
1726
  },
1590
1727
  React.createElement(CaretGlyph, { open }),
1591
- React.createElement(FolderGlyph),
1728
+ looks.glyph
1729
+ ? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
1730
+ : React.createElement(FolderGlyph),
1592
1731
  looks.tone
1593
1732
  ? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
1594
1733
  : null,
@@ -1669,6 +1808,8 @@ window.__ModuleLoader__.load({
1669
1808
  : null,
1670
1809
  switcherCard,
1671
1810
  sessionMenu,
1811
+ glyphMenu,
1812
+ noteMenu,
1672
1813
  archivedMenu,
1673
1814
  paintMenu,
1674
1815
  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.16.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",