@aryacode/dsh-aryacode-polish 0.12.1 → 0.13.1
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 +25 -0
- package/client/workspace.js +229 -48
- package/docs/better-workspace.md +20 -0
- package/lib/client.js +230 -49
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.1] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Rename a workspace in place.** The workspace menu opens an input under the search
|
|
8
|
+
box; Enter (or blurring) sends `workspaces.rename`, Escape abandons it. Renaming to
|
|
9
|
+
`Work/Client A` is also how a workspace joins a folder.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `planReorder` ignores ids the Host no longer has, so a saved order that names a
|
|
14
|
+
deleted workspace cannot shuffle the survivors into the wrong slots.
|
|
15
|
+
|
|
16
|
+
## [0.13.0] - 2026-10-28
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Folders from titles.** A workspace named `Work/Client A` files itself under a
|
|
21
|
+
collapsible `Work` row, with the folder's session count on the right. The grouping is
|
|
22
|
+
derived from the label and written nowhere, so renaming the workspace moves it.
|
|
23
|
+
- **Order presets.** The workspace menu can "Remember this order" and later "Restore
|
|
24
|
+
saved order", which replays the Host's only reorder verb — `insertBefore` — through a
|
|
25
|
+
pure `planReorder(current, wanted)` plan, stored under
|
|
26
|
+
`aryacode-polish:workspace-order.v1`.
|
|
27
|
+
|
|
3
28
|
## [0.12.1] - 2026-10-28
|
|
4
29
|
|
|
5
30
|
### Fixed
|
package/client/workspace.js
CHANGED
|
@@ -383,6 +383,79 @@ export function moveBefore(list, movingId, beforeId) {
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
const LOOK_KEY = 'aryacode-polish:workspace-appearance.v1'
|
|
386
|
+
const ORDER_KEY = 'aryacode-polish:workspace-order.v1'
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* A workspace whose label carries a path ("Work/Client A") files itself under
|
|
390
|
+
* "Work". The grouping is purely visual and derived from the title, so nothing is
|
|
391
|
+
* written to the Host and renaming a workspace is enough to move it between
|
|
392
|
+
* folders — the same idea as the reference plugin, without its "/" toggle.
|
|
393
|
+
*/
|
|
394
|
+
export function folderKey(workspace) {
|
|
395
|
+
const label = workspaceLabel(workspace)
|
|
396
|
+
const cut = label.lastIndexOf('/')
|
|
397
|
+
if (cut <= 0 || cut === label.length - 1) return undefined
|
|
398
|
+
return label.slice(0, cut).trim() || undefined
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function readOrder(storage) {
|
|
402
|
+
try {
|
|
403
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
404
|
+
if (!store) return []
|
|
405
|
+
const raw = store.getItem(ORDER_KEY)
|
|
406
|
+
if (!raw) return []
|
|
407
|
+
const parsed = JSON.parse(raw)
|
|
408
|
+
return Array.isArray(parsed) ? parsed.filter((id) => typeof id === 'string') : []
|
|
409
|
+
} catch {
|
|
410
|
+
return []
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function writeOrder(ids, storage) {
|
|
415
|
+
try {
|
|
416
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
417
|
+
if (!store) return
|
|
418
|
+
store.setItem(ORDER_KEY, JSON.stringify(Array.isArray(ids) ? ids : []))
|
|
419
|
+
} catch {
|
|
420
|
+
/* storage is a convenience here, never a requirement */
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The insertBefore calls that turn one order into another. `insertBefore(moving, before)`
|
|
426
|
+
* is the only reorder verb the Host offers, so the plan walks the wanted order and moves
|
|
427
|
+
* each workspace in front of whatever currently sits at that slot.
|
|
428
|
+
*/
|
|
429
|
+
export function planReorder(currentIds, wantedIds) {
|
|
430
|
+
const current = Array.isArray(currentIds) ? currentIds.slice() : []
|
|
431
|
+
const ops = []
|
|
432
|
+
// Only ids the Host still has can move; a saved order that names a deleted
|
|
433
|
+
// workspace must not shuffle the rest into the wrong slots.
|
|
434
|
+
const wanted = (Array.isArray(wantedIds) ? wantedIds : []).filter((id) => current.indexOf(id) !== -1)
|
|
435
|
+
for (let i = 0; i < wanted.length; i += 1) {
|
|
436
|
+
const id = wanted[i]
|
|
437
|
+
const from = current.indexOf(id)
|
|
438
|
+
if (from === -1 || from === i) continue
|
|
439
|
+
const beforeId = current[i]
|
|
440
|
+
ops.push({ movingId: id, beforeId })
|
|
441
|
+
current.splice(from, 1)
|
|
442
|
+
current.splice(i, 0, id)
|
|
443
|
+
}
|
|
444
|
+
return ops
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const WS_FOLDER_ROW = {
|
|
448
|
+
display: 'flex',
|
|
449
|
+
alignItems: 'center',
|
|
450
|
+
gap: '5px',
|
|
451
|
+
padding: '3px 6px 2px',
|
|
452
|
+
fontSize: 'calc(' + 'var(--dsh-content-font-size-secondary, 13px)' + ' - 2px)',
|
|
453
|
+
letterSpacing: '0.02em',
|
|
454
|
+
textTransform: 'uppercase',
|
|
455
|
+
color: 'var(--dsw-alias-label-tertiary, #61666b)',
|
|
456
|
+
cursor: 'pointer',
|
|
457
|
+
userSelect: 'none',
|
|
458
|
+
}
|
|
386
459
|
|
|
387
460
|
export const TONE_ORDER = ['slate', 'blue', 'green', 'amber', 'rose', 'violet']
|
|
388
461
|
|
|
@@ -524,6 +597,22 @@ export function BetterWorkspace(props) {
|
|
|
524
597
|
const [drag, setDrag] = React.useState(null)
|
|
525
598
|
const [dropping, setDropping] = React.useState(null)
|
|
526
599
|
const [look, setLook] = React.useState(() => readAppearance())
|
|
600
|
+
const [folders, setFolders] = React.useState({})
|
|
601
|
+
const [renaming, setRenaming] = React.useState(null)
|
|
602
|
+
const commitRename = async () => {
|
|
603
|
+
const draft = renaming
|
|
604
|
+
setRenaming(null)
|
|
605
|
+
if (!draft || !services.renameWorkspace) return
|
|
606
|
+
const title = String(draft.value || '').trim()
|
|
607
|
+
if (title === '' || title === draft.label) return
|
|
608
|
+
try {
|
|
609
|
+
await services.renameWorkspace(draft.workspaceId, title)
|
|
610
|
+
setNotice('Renamed to ' + title + '.')
|
|
611
|
+
} catch {
|
|
612
|
+
setNotice('Could not rename that workspace.')
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const [savedOrder, setSavedOrder] = React.useState(() => readOrder())
|
|
527
616
|
const paintLook = (workspaceId, patch) => {
|
|
528
617
|
setLook((prev) => {
|
|
529
618
|
const next = withAppearance(prev, workspaceId, patch)
|
|
@@ -545,7 +634,12 @@ export function BetterWorkspace(props) {
|
|
|
545
634
|
)
|
|
546
635
|
const allRows = workspaceRows(workspaceList, sessionList)
|
|
547
636
|
const rows = filterTree(allRows, query, pinned)
|
|
548
|
-
const
|
|
637
|
+
const folderOf = (workspace) => folderKey(workspace)
|
|
638
|
+
const visibleRows = rows.filter((group) => {
|
|
639
|
+
const key = folderOf(group.workspace)
|
|
640
|
+
return key === undefined || folders[key] !== true
|
|
641
|
+
})
|
|
642
|
+
const walk = flattenRows(visibleRows, expanded, query)
|
|
549
643
|
const runtime = React.useMemo(() => menuRuntime(ctx, slots, standard), [ctx, slots, standard.wide])
|
|
550
644
|
const RowBoundary = React.useMemo(() => rowBoundary(), [])
|
|
551
645
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
@@ -672,6 +766,47 @@ export function BetterWorkspace(props) {
|
|
|
672
766
|
try { await navigator.clipboard.writeText(menu.workspace.path) } catch {}
|
|
673
767
|
},
|
|
674
768
|
}, 'Copy path'),
|
|
769
|
+
React.createElement('button', {
|
|
770
|
+
type: 'button',
|
|
771
|
+
'data-polish-ws-rename': 'open',
|
|
772
|
+
style: WS_MENU_ITEM,
|
|
773
|
+
onClick: () => {
|
|
774
|
+
const workspace = menu.workspace
|
|
775
|
+
closeMenu()
|
|
776
|
+
setNotice('')
|
|
777
|
+
setRenaming({ workspaceId: workspace.workspaceId, label: workspaceLabel(workspace), value: workspaceLabel(workspace) })
|
|
778
|
+
},
|
|
779
|
+
}, 'Rename workspace…'),
|
|
780
|
+
React.createElement('button', {
|
|
781
|
+
type: 'button',
|
|
782
|
+
'data-polish-order': 'remember',
|
|
783
|
+
style: WS_MENU_ITEM,
|
|
784
|
+
onClick: () => {
|
|
785
|
+
closeMenu()
|
|
786
|
+
const ids = workspaceList.map((workspace) => workspace.workspaceId)
|
|
787
|
+
writeOrder(ids)
|
|
788
|
+
setSavedOrder(ids)
|
|
789
|
+
setNotice('Order saved.')
|
|
790
|
+
},
|
|
791
|
+
}, 'Remember this order'),
|
|
792
|
+
savedOrder.length === 0
|
|
793
|
+
? null
|
|
794
|
+
: React.createElement('button', {
|
|
795
|
+
type: 'button',
|
|
796
|
+
'data-polish-order': 'restore',
|
|
797
|
+
style: WS_MENU_ITEM,
|
|
798
|
+
onClick: async () => {
|
|
799
|
+
closeMenu()
|
|
800
|
+
const ops = planReorder(workspaceList.map((workspace) => workspace.workspaceId), savedOrder)
|
|
801
|
+
if (ops.length === 0) { setNotice('Already in the saved order.'); return }
|
|
802
|
+
try {
|
|
803
|
+
for (const op of ops) await services.reorderWorkspace(op.movingId, op.beforeId)
|
|
804
|
+
setNotice('Order restored.')
|
|
805
|
+
} catch {
|
|
806
|
+
setNotice('Could not restore that order.')
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
}, 'Restore saved order'),
|
|
675
810
|
React.createElement('button', {
|
|
676
811
|
type: 'button',
|
|
677
812
|
'data-polish-ws-appearance': 'open',
|
|
@@ -808,6 +943,26 @@ export function BetterWorkspace(props) {
|
|
|
808
943
|
}))
|
|
809
944
|
}
|
|
810
945
|
|
|
946
|
+
if (renaming) {
|
|
947
|
+
children.push(React.createElement('input', {
|
|
948
|
+
key: 'rename',
|
|
949
|
+
'data-polish-ws-rename-input': 'true',
|
|
950
|
+
autoFocus: true,
|
|
951
|
+
value: renaming.value,
|
|
952
|
+
placeholder: 'Workspace name (use Work/Name to file it)',
|
|
953
|
+
'aria-label': 'Workspace name',
|
|
954
|
+
style: WS_FILTER,
|
|
955
|
+
onChange: (event) => setRenaming(Object.assign({}, renaming, { value: event.target.value })),
|
|
956
|
+
onKeyDown: (event) => {
|
|
957
|
+
if (event.key === 'Escape') { event.stopPropagation(); setRenaming(null); return }
|
|
958
|
+
if (event.key !== 'Enter') return
|
|
959
|
+
event.preventDefault()
|
|
960
|
+
void commitRename()
|
|
961
|
+
},
|
|
962
|
+
onBlur: () => { void commitRename() },
|
|
963
|
+
}))
|
|
964
|
+
}
|
|
965
|
+
|
|
811
966
|
if (rows.length === 0) {
|
|
812
967
|
children.push(React.createElement('div', {
|
|
813
968
|
key: 'empty',
|
|
@@ -816,14 +971,82 @@ export function BetterWorkspace(props) {
|
|
|
816
971
|
}
|
|
817
972
|
|
|
818
973
|
let index = -1
|
|
974
|
+
const announced = {}
|
|
975
|
+
|
|
976
|
+
const sessionRow = (workspace, row, groupIndex) => {
|
|
977
|
+
const rowFocused = cursor === groupIndex
|
|
978
|
+
const rowHovered = hover === 'session:' + row.id
|
|
979
|
+
const lifted = dropping === 'session:' + row.id
|
|
980
|
+
return React.createElement('div', {
|
|
981
|
+
key: 'session:' + row.id,
|
|
982
|
+
'data-polish-ws-session': row.id,
|
|
983
|
+
draggable: true,
|
|
984
|
+
style: Object.assign({}, WS_ROW, {
|
|
985
|
+
paddingLeft: '24px',
|
|
986
|
+
opacity: drag && drag.kind === 'session' && drag.id === row.id ? 0.5 : undefined,
|
|
987
|
+
background: lifted
|
|
988
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
989
|
+
: (rowFocused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
990
|
+
boxShadow: rowFocused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
991
|
+
}),
|
|
992
|
+
onClick: () => services.openSession(row.id),
|
|
993
|
+
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
994
|
+
onMouseEnter: () => setHover('session:' + row.id),
|
|
995
|
+
onMouseLeave: () => setHover(null),
|
|
996
|
+
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
997
|
+
onDragOver: (event) => {
|
|
998
|
+
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
999
|
+
event.preventDefault()
|
|
1000
|
+
setDropping('session:' + row.id)
|
|
1001
|
+
},
|
|
1002
|
+
onDragLeave: () => setDropping(null),
|
|
1003
|
+
onDrop: (event) => { event.preventDefault(); dropOnSession(workspace.workspaceId, row.id) },
|
|
1004
|
+
},
|
|
1005
|
+
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
1006
|
+
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
1007
|
+
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
1008
|
+
rowHovered || rowFocused
|
|
1009
|
+
? React.createElement('button', {
|
|
1010
|
+
type: 'button',
|
|
1011
|
+
'data-polish-ws-menu': 'session',
|
|
1012
|
+
'aria-label': 'Session menu',
|
|
1013
|
+
style: WS_ROW_ACTION,
|
|
1014
|
+
onClick: (event) => openMenu(event, row, workspace),
|
|
1015
|
+
}, React.createElement(DotsGlyph))
|
|
1016
|
+
: null)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
819
1019
|
rows.forEach((group) => {
|
|
820
1020
|
const workspace = group.workspace
|
|
821
1021
|
const sessions = group.sessions
|
|
1022
|
+
const folder = folderOf(workspace)
|
|
1023
|
+
const folded = folder !== undefined && folders[folder] === true
|
|
1024
|
+
|
|
1025
|
+
if (folder !== undefined && !announced[folder]) {
|
|
1026
|
+
announced[folder] = true
|
|
1027
|
+
const inside = rows.filter((entry) => folderOf(entry.workspace) === folder)
|
|
1028
|
+
children.push(React.createElement('div', {
|
|
1029
|
+
key: 'folder:' + folder,
|
|
1030
|
+
'data-polish-ws-folder': folder,
|
|
1031
|
+
role: 'group',
|
|
1032
|
+
'aria-label': folder,
|
|
1033
|
+
title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
|
|
1034
|
+
style: WS_FOLDER_ROW,
|
|
1035
|
+
onClick: () => setFolders((prev) => Object.assign({}, prev, { [folder]: prev[folder] !== true })),
|
|
1036
|
+
},
|
|
1037
|
+
React.createElement('span', { 'aria-hidden': 'true' }, folded ? '›' : '⌄'),
|
|
1038
|
+
React.createElement('span', { style: WS_TITLE }, folder),
|
|
1039
|
+
React.createElement('span', { style: WS_COUNT }, String(inside.reduce((sum, entry) => sum + entry.sessions.length, 0)))))
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (folded) return
|
|
1043
|
+
|
|
822
1044
|
index += 1
|
|
823
1045
|
const open = expanded[workspace.workspaceId] !== false
|
|
824
1046
|
const focused = cursor === index
|
|
825
1047
|
const hovered = hover === 'workspace:' + workspace.workspaceId
|
|
826
1048
|
const dropHere = dropping === 'workspace:' + workspace.workspaceId
|
|
1049
|
+
const looks = appearanceOf(look, workspace.workspaceId)
|
|
827
1050
|
|
|
828
1051
|
children.push(React.createElement('div', {
|
|
829
1052
|
key: 'workspace:' + workspace.workspaceId,
|
|
@@ -855,15 +1078,11 @@ export function BetterWorkspace(props) {
|
|
|
855
1078
|
},
|
|
856
1079
|
React.createElement(CaretGlyph, { open }),
|
|
857
1080
|
React.createElement(FolderGlyph),
|
|
858
|
-
|
|
859
|
-
? React.createElement('span', {
|
|
860
|
-
style: Object.assign({}, WS_TONE, { background: TONE_COLOR[appearanceOf(look, workspace.workspaceId).tone] }),
|
|
861
|
-
})
|
|
1081
|
+
looks.tone
|
|
1082
|
+
? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
|
|
862
1083
|
: null,
|
|
863
1084
|
React.createElement('span', {
|
|
864
|
-
style: Object.assign({}, WS_TITLE, {
|
|
865
|
-
fontWeight: appearanceOf(look, workspace.workspaceId).strong ? 600 : undefined,
|
|
866
|
-
}),
|
|
1085
|
+
style: Object.assign({}, WS_TITLE, { fontWeight: looks.strong ? 600 : undefined }),
|
|
867
1086
|
}, workspaceLabel(workspace)),
|
|
868
1087
|
React.createElement('span', { style: WS_COUNT }, String(sessions.length)),
|
|
869
1088
|
hovered || focused
|
|
@@ -879,46 +1098,7 @@ export function BetterWorkspace(props) {
|
|
|
879
1098
|
if (open) {
|
|
880
1099
|
sessions.forEach((row) => {
|
|
881
1100
|
index += 1
|
|
882
|
-
|
|
883
|
-
const rowHovered = hover === 'session:' + row.id
|
|
884
|
-
const lifted = dropping === 'session:' + row.id
|
|
885
|
-
children.push(React.createElement('div', {
|
|
886
|
-
key: 'session:' + row.id,
|
|
887
|
-
'data-polish-ws-session': row.id,
|
|
888
|
-
draggable: true,
|
|
889
|
-
style: Object.assign({}, WS_ROW, {
|
|
890
|
-
paddingLeft: '24px',
|
|
891
|
-
opacity: drag && drag.kind === 'session' && drag.id === row.id ? 0.5 : undefined,
|
|
892
|
-
background: lifted
|
|
893
|
-
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
894
|
-
: (rowFocused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
895
|
-
boxShadow: rowFocused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
896
|
-
}),
|
|
897
|
-
onClick: () => services.openSession(row.id),
|
|
898
|
-
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
899
|
-
onMouseEnter: () => setHover('session:' + row.id),
|
|
900
|
-
onMouseLeave: () => setHover(null),
|
|
901
|
-
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
902
|
-
onDragOver: (event) => {
|
|
903
|
-
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
904
|
-
event.preventDefault()
|
|
905
|
-
setDropping('session:' + row.id)
|
|
906
|
-
},
|
|
907
|
-
onDragLeave: () => setDropping(null),
|
|
908
|
-
onDrop: (event) => { event.preventDefault(); dropOnSession(workspace.workspaceId, row.id) },
|
|
909
|
-
},
|
|
910
|
-
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
911
|
-
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
912
|
-
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
913
|
-
rowHovered || rowFocused
|
|
914
|
-
? React.createElement('button', {
|
|
915
|
-
type: 'button',
|
|
916
|
-
'data-polish-ws-menu': 'session',
|
|
917
|
-
'aria-label': 'Session menu',
|
|
918
|
-
style: WS_ROW_ACTION,
|
|
919
|
-
onClick: (event) => openMenu(event, row, workspace),
|
|
920
|
-
}, React.createElement(DotsGlyph))
|
|
921
|
-
: null))
|
|
1101
|
+
children.push(sessionRow(workspace, row, index))
|
|
922
1102
|
})
|
|
923
1103
|
}
|
|
924
1104
|
})
|
|
@@ -1071,6 +1251,7 @@ export function installWorkspaceSeat(ctx, configForms, extras = {}) {
|
|
|
1071
1251
|
reorderWorkspace: (movingId, beforeId) => seated.workspaces.insertBefore(movingId, beforeId),
|
|
1072
1252
|
reorderSession: (workspaceId, movingId, beforeId) => seated.workspaces.insertSessionBefore(workspaceId, movingId, beforeId),
|
|
1073
1253
|
removeWorkspace: (id) => seated.workspaces.delete(id),
|
|
1254
|
+
renameWorkspace: (id, title) => seated.workspaces.rename(id, title),
|
|
1074
1255
|
openFolder: async (path) => {
|
|
1075
1256
|
const answer = await seated.remote.session.openWorkspacePath({ path })
|
|
1076
1257
|
if (answer && answer.ok === false) throw new Error('cannot open the folder')
|
package/docs/better-workspace.md
CHANGED
|
@@ -114,3 +114,23 @@ switch. The slot hands over `{ open, anchorRef, selectedId, onPick, onClose }`:
|
|
|
114
114
|
is portalled to the anchor's bottom-left only while `open` is true (an unconditional
|
|
115
115
|
list floated over the Hero copy), it shows the same tone dots and bold labels as the
|
|
116
116
|
sidebar, and picking a row calls `onPick(workspaceId)` then `onClose()`.
|
|
117
|
+
|
|
118
|
+
## Folders from titles
|
|
119
|
+
|
|
120
|
+
A workspace whose label carries a path files itself under the prefix: rename
|
|
121
|
+
`Alpha` to `Work/Alpha` (workspace menu → **Rename workspace…**) and a collapsible
|
|
122
|
+
`WORK` row appears, carrying the session count of everything filed inside it. The
|
|
123
|
+
group is derived on every render from `folderKey(workspace)` — nothing is written to the
|
|
124
|
+
Host, nothing is cached, and renaming the workspace out of the prefix puts it back in
|
|
125
|
+
the flat list. `folderKey` refuses a leading slash, a trailing slash and a bare label, so
|
|
126
|
+
ordinary names never grow a folder.
|
|
127
|
+
|
|
128
|
+
## Order presets
|
|
129
|
+
|
|
130
|
+
**Remember this order** stores the current workspace ids under
|
|
131
|
+
`aryacode-polish:workspace-order.v1`; **Restore saved order** replays them. The Host
|
|
132
|
+
offers exactly one reorder verb (`insertBefore(movingId, beforeId)`), so restoring is
|
|
133
|
+
`planReorder(current, wanted)` — a pure function that walks the wanted order and emits
|
|
134
|
+
one move per workspace that sits in the wrong slot. Ids the Host no longer has are
|
|
135
|
+
filtered out first, so a preset that names a deleted workspace cannot shuffle the
|
|
136
|
+
survivors into the wrong places.
|
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.
|
|
56
|
+
const CLIENT_VERSION = "0.13.1"
|
|
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. */
|
|
@@ -584,6 +584,79 @@ window.__ModuleLoader__.load({
|
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
const LOOK_KEY = 'aryacode-polish:workspace-appearance.v1'
|
|
587
|
+
const ORDER_KEY = 'aryacode-polish:workspace-order.v1'
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* A workspace whose label carries a path ("Work/Client A") files itself under
|
|
591
|
+
* "Work". The grouping is purely visual and derived from the title, so nothing is
|
|
592
|
+
* written to the Host and renaming a workspace is enough to move it between
|
|
593
|
+
* folders — the same idea as the reference plugin, without its "/" toggle.
|
|
594
|
+
*/
|
|
595
|
+
function folderKey(workspace) {
|
|
596
|
+
const label = workspaceLabel(workspace)
|
|
597
|
+
const cut = label.lastIndexOf('/')
|
|
598
|
+
if (cut <= 0 || cut === label.length - 1) return undefined
|
|
599
|
+
return label.slice(0, cut).trim() || undefined
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function readOrder(storage) {
|
|
603
|
+
try {
|
|
604
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
605
|
+
if (!store) return []
|
|
606
|
+
const raw = store.getItem(ORDER_KEY)
|
|
607
|
+
if (!raw) return []
|
|
608
|
+
const parsed = JSON.parse(raw)
|
|
609
|
+
return Array.isArray(parsed) ? parsed.filter((id) => typeof id === 'string') : []
|
|
610
|
+
} catch {
|
|
611
|
+
return []
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function writeOrder(ids, storage) {
|
|
616
|
+
try {
|
|
617
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
618
|
+
if (!store) return
|
|
619
|
+
store.setItem(ORDER_KEY, JSON.stringify(Array.isArray(ids) ? ids : []))
|
|
620
|
+
} catch {
|
|
621
|
+
/* storage is a convenience here, never a requirement */
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* The insertBefore calls that turn one order into another. `insertBefore(moving, before)`
|
|
627
|
+
* is the only reorder verb the Host offers, so the plan walks the wanted order and moves
|
|
628
|
+
* each workspace in front of whatever currently sits at that slot.
|
|
629
|
+
*/
|
|
630
|
+
function planReorder(currentIds, wantedIds) {
|
|
631
|
+
const current = Array.isArray(currentIds) ? currentIds.slice() : []
|
|
632
|
+
const ops = []
|
|
633
|
+
// Only ids the Host still has can move; a saved order that names a deleted
|
|
634
|
+
// workspace must not shuffle the rest into the wrong slots.
|
|
635
|
+
const wanted = (Array.isArray(wantedIds) ? wantedIds : []).filter((id) => current.indexOf(id) !== -1)
|
|
636
|
+
for (let i = 0; i < wanted.length; i += 1) {
|
|
637
|
+
const id = wanted[i]
|
|
638
|
+
const from = current.indexOf(id)
|
|
639
|
+
if (from === -1 || from === i) continue
|
|
640
|
+
const beforeId = current[i]
|
|
641
|
+
ops.push({ movingId: id, beforeId })
|
|
642
|
+
current.splice(from, 1)
|
|
643
|
+
current.splice(i, 0, id)
|
|
644
|
+
}
|
|
645
|
+
return ops
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const WS_FOLDER_ROW = {
|
|
649
|
+
display: 'flex',
|
|
650
|
+
alignItems: 'center',
|
|
651
|
+
gap: '5px',
|
|
652
|
+
padding: '3px 6px 2px',
|
|
653
|
+
fontSize: 'calc(' + 'var(--dsh-content-font-size-secondary, 13px)' + ' - 2px)',
|
|
654
|
+
letterSpacing: '0.02em',
|
|
655
|
+
textTransform: 'uppercase',
|
|
656
|
+
color: 'var(--dsw-alias-label-tertiary, #61666b)',
|
|
657
|
+
cursor: 'pointer',
|
|
658
|
+
userSelect: 'none',
|
|
659
|
+
}
|
|
587
660
|
|
|
588
661
|
const TONE_ORDER = ['slate', 'blue', 'green', 'amber', 'rose', 'violet']
|
|
589
662
|
|
|
@@ -725,6 +798,22 @@ window.__ModuleLoader__.load({
|
|
|
725
798
|
const [drag, setDrag] = React.useState(null)
|
|
726
799
|
const [dropping, setDropping] = React.useState(null)
|
|
727
800
|
const [look, setLook] = React.useState(() => readAppearance())
|
|
801
|
+
const [folders, setFolders] = React.useState({})
|
|
802
|
+
const [renaming, setRenaming] = React.useState(null)
|
|
803
|
+
const commitRename = async () => {
|
|
804
|
+
const draft = renaming
|
|
805
|
+
setRenaming(null)
|
|
806
|
+
if (!draft || !services.renameWorkspace) return
|
|
807
|
+
const title = String(draft.value || '').trim()
|
|
808
|
+
if (title === '' || title === draft.label) return
|
|
809
|
+
try {
|
|
810
|
+
await services.renameWorkspace(draft.workspaceId, title)
|
|
811
|
+
setNotice('Renamed to ' + title + '.')
|
|
812
|
+
} catch {
|
|
813
|
+
setNotice('Could not rename that workspace.')
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const [savedOrder, setSavedOrder] = React.useState(() => readOrder())
|
|
728
817
|
const paintLook = (workspaceId, patch) => {
|
|
729
818
|
setLook((prev) => {
|
|
730
819
|
const next = withAppearance(prev, workspaceId, patch)
|
|
@@ -746,7 +835,12 @@ window.__ModuleLoader__.load({
|
|
|
746
835
|
)
|
|
747
836
|
const allRows = workspaceRows(workspaceList, sessionList)
|
|
748
837
|
const rows = filterTree(allRows, query, pinned)
|
|
749
|
-
const
|
|
838
|
+
const folderOf = (workspace) => folderKey(workspace)
|
|
839
|
+
const visibleRows = rows.filter((group) => {
|
|
840
|
+
const key = folderOf(group.workspace)
|
|
841
|
+
return key === undefined || folders[key] !== true
|
|
842
|
+
})
|
|
843
|
+
const walk = flattenRows(visibleRows, expanded, query)
|
|
750
844
|
const runtime = React.useMemo(() => menuRuntime(ctx, slots, standard), [ctx, slots, standard.wide])
|
|
751
845
|
const RowBoundary = React.useMemo(() => rowBoundary(), [])
|
|
752
846
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
@@ -873,6 +967,47 @@ window.__ModuleLoader__.load({
|
|
|
873
967
|
try { await navigator.clipboard.writeText(menu.workspace.path) } catch {}
|
|
874
968
|
},
|
|
875
969
|
}, 'Copy path'),
|
|
970
|
+
React.createElement('button', {
|
|
971
|
+
type: 'button',
|
|
972
|
+
'data-polish-ws-rename': 'open',
|
|
973
|
+
style: WS_MENU_ITEM,
|
|
974
|
+
onClick: () => {
|
|
975
|
+
const workspace = menu.workspace
|
|
976
|
+
closeMenu()
|
|
977
|
+
setNotice('')
|
|
978
|
+
setRenaming({ workspaceId: workspace.workspaceId, label: workspaceLabel(workspace), value: workspaceLabel(workspace) })
|
|
979
|
+
},
|
|
980
|
+
}, 'Rename workspace…'),
|
|
981
|
+
React.createElement('button', {
|
|
982
|
+
type: 'button',
|
|
983
|
+
'data-polish-order': 'remember',
|
|
984
|
+
style: WS_MENU_ITEM,
|
|
985
|
+
onClick: () => {
|
|
986
|
+
closeMenu()
|
|
987
|
+
const ids = workspaceList.map((workspace) => workspace.workspaceId)
|
|
988
|
+
writeOrder(ids)
|
|
989
|
+
setSavedOrder(ids)
|
|
990
|
+
setNotice('Order saved.')
|
|
991
|
+
},
|
|
992
|
+
}, 'Remember this order'),
|
|
993
|
+
savedOrder.length === 0
|
|
994
|
+
? null
|
|
995
|
+
: React.createElement('button', {
|
|
996
|
+
type: 'button',
|
|
997
|
+
'data-polish-order': 'restore',
|
|
998
|
+
style: WS_MENU_ITEM,
|
|
999
|
+
onClick: async () => {
|
|
1000
|
+
closeMenu()
|
|
1001
|
+
const ops = planReorder(workspaceList.map((workspace) => workspace.workspaceId), savedOrder)
|
|
1002
|
+
if (ops.length === 0) { setNotice('Already in the saved order.'); return }
|
|
1003
|
+
try {
|
|
1004
|
+
for (const op of ops) await services.reorderWorkspace(op.movingId, op.beforeId)
|
|
1005
|
+
setNotice('Order restored.')
|
|
1006
|
+
} catch {
|
|
1007
|
+
setNotice('Could not restore that order.')
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
}, 'Restore saved order'),
|
|
876
1011
|
React.createElement('button', {
|
|
877
1012
|
type: 'button',
|
|
878
1013
|
'data-polish-ws-appearance': 'open',
|
|
@@ -1009,6 +1144,26 @@ window.__ModuleLoader__.load({
|
|
|
1009
1144
|
}))
|
|
1010
1145
|
}
|
|
1011
1146
|
|
|
1147
|
+
if (renaming) {
|
|
1148
|
+
children.push(React.createElement('input', {
|
|
1149
|
+
key: 'rename',
|
|
1150
|
+
'data-polish-ws-rename-input': 'true',
|
|
1151
|
+
autoFocus: true,
|
|
1152
|
+
value: renaming.value,
|
|
1153
|
+
placeholder: 'Workspace name (use Work/Name to file it)',
|
|
1154
|
+
'aria-label': 'Workspace name',
|
|
1155
|
+
style: WS_FILTER,
|
|
1156
|
+
onChange: (event) => setRenaming(Object.assign({}, renaming, { value: event.target.value })),
|
|
1157
|
+
onKeyDown: (event) => {
|
|
1158
|
+
if (event.key === 'Escape') { event.stopPropagation(); setRenaming(null); return }
|
|
1159
|
+
if (event.key !== 'Enter') return
|
|
1160
|
+
event.preventDefault()
|
|
1161
|
+
void commitRename()
|
|
1162
|
+
},
|
|
1163
|
+
onBlur: () => { void commitRename() },
|
|
1164
|
+
}))
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1012
1167
|
if (rows.length === 0) {
|
|
1013
1168
|
children.push(React.createElement('div', {
|
|
1014
1169
|
key: 'empty',
|
|
@@ -1017,14 +1172,82 @@ window.__ModuleLoader__.load({
|
|
|
1017
1172
|
}
|
|
1018
1173
|
|
|
1019
1174
|
let index = -1
|
|
1175
|
+
const announced = {}
|
|
1176
|
+
|
|
1177
|
+
const sessionRow = (workspace, row, groupIndex) => {
|
|
1178
|
+
const rowFocused = cursor === groupIndex
|
|
1179
|
+
const rowHovered = hover === 'session:' + row.id
|
|
1180
|
+
const lifted = dropping === 'session:' + row.id
|
|
1181
|
+
return React.createElement('div', {
|
|
1182
|
+
key: 'session:' + row.id,
|
|
1183
|
+
'data-polish-ws-session': row.id,
|
|
1184
|
+
draggable: true,
|
|
1185
|
+
style: Object.assign({}, WS_ROW, {
|
|
1186
|
+
paddingLeft: '24px',
|
|
1187
|
+
opacity: drag && drag.kind === 'session' && drag.id === row.id ? 0.5 : undefined,
|
|
1188
|
+
background: lifted
|
|
1189
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
1190
|
+
: (rowFocused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
1191
|
+
boxShadow: rowFocused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
1192
|
+
}),
|
|
1193
|
+
onClick: () => services.openSession(row.id),
|
|
1194
|
+
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
1195
|
+
onMouseEnter: () => setHover('session:' + row.id),
|
|
1196
|
+
onMouseLeave: () => setHover(null),
|
|
1197
|
+
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
1198
|
+
onDragOver: (event) => {
|
|
1199
|
+
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
1200
|
+
event.preventDefault()
|
|
1201
|
+
setDropping('session:' + row.id)
|
|
1202
|
+
},
|
|
1203
|
+
onDragLeave: () => setDropping(null),
|
|
1204
|
+
onDrop: (event) => { event.preventDefault(); dropOnSession(workspace.workspaceId, row.id) },
|
|
1205
|
+
},
|
|
1206
|
+
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
1207
|
+
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
1208
|
+
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
1209
|
+
rowHovered || rowFocused
|
|
1210
|
+
? React.createElement('button', {
|
|
1211
|
+
type: 'button',
|
|
1212
|
+
'data-polish-ws-menu': 'session',
|
|
1213
|
+
'aria-label': 'Session menu',
|
|
1214
|
+
style: WS_ROW_ACTION,
|
|
1215
|
+
onClick: (event) => openMenu(event, row, workspace),
|
|
1216
|
+
}, React.createElement(DotsGlyph))
|
|
1217
|
+
: null)
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1020
1220
|
rows.forEach((group) => {
|
|
1021
1221
|
const workspace = group.workspace
|
|
1022
1222
|
const sessions = group.sessions
|
|
1223
|
+
const folder = folderOf(workspace)
|
|
1224
|
+
const folded = folder !== undefined && folders[folder] === true
|
|
1225
|
+
|
|
1226
|
+
if (folder !== undefined && !announced[folder]) {
|
|
1227
|
+
announced[folder] = true
|
|
1228
|
+
const inside = rows.filter((entry) => folderOf(entry.workspace) === folder)
|
|
1229
|
+
children.push(React.createElement('div', {
|
|
1230
|
+
key: 'folder:' + folder,
|
|
1231
|
+
'data-polish-ws-folder': folder,
|
|
1232
|
+
role: 'group',
|
|
1233
|
+
'aria-label': folder,
|
|
1234
|
+
title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
|
|
1235
|
+
style: WS_FOLDER_ROW,
|
|
1236
|
+
onClick: () => setFolders((prev) => Object.assign({}, prev, { [folder]: prev[folder] !== true })),
|
|
1237
|
+
},
|
|
1238
|
+
React.createElement('span', { 'aria-hidden': 'true' }, folded ? '›' : '⌄'),
|
|
1239
|
+
React.createElement('span', { style: WS_TITLE }, folder),
|
|
1240
|
+
React.createElement('span', { style: WS_COUNT }, String(inside.reduce((sum, entry) => sum + entry.sessions.length, 0)))))
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (folded) return
|
|
1244
|
+
|
|
1023
1245
|
index += 1
|
|
1024
1246
|
const open = expanded[workspace.workspaceId] !== false
|
|
1025
1247
|
const focused = cursor === index
|
|
1026
1248
|
const hovered = hover === 'workspace:' + workspace.workspaceId
|
|
1027
1249
|
const dropHere = dropping === 'workspace:' + workspace.workspaceId
|
|
1250
|
+
const looks = appearanceOf(look, workspace.workspaceId)
|
|
1028
1251
|
|
|
1029
1252
|
children.push(React.createElement('div', {
|
|
1030
1253
|
key: 'workspace:' + workspace.workspaceId,
|
|
@@ -1056,15 +1279,11 @@ window.__ModuleLoader__.load({
|
|
|
1056
1279
|
},
|
|
1057
1280
|
React.createElement(CaretGlyph, { open }),
|
|
1058
1281
|
React.createElement(FolderGlyph),
|
|
1059
|
-
|
|
1060
|
-
? React.createElement('span', {
|
|
1061
|
-
style: Object.assign({}, WS_TONE, { background: TONE_COLOR[appearanceOf(look, workspace.workspaceId).tone] }),
|
|
1062
|
-
})
|
|
1282
|
+
looks.tone
|
|
1283
|
+
? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
|
|
1063
1284
|
: null,
|
|
1064
1285
|
React.createElement('span', {
|
|
1065
|
-
style: Object.assign({}, WS_TITLE, {
|
|
1066
|
-
fontWeight: appearanceOf(look, workspace.workspaceId).strong ? 600 : undefined,
|
|
1067
|
-
}),
|
|
1286
|
+
style: Object.assign({}, WS_TITLE, { fontWeight: looks.strong ? 600 : undefined }),
|
|
1068
1287
|
}, workspaceLabel(workspace)),
|
|
1069
1288
|
React.createElement('span', { style: WS_COUNT }, String(sessions.length)),
|
|
1070
1289
|
hovered || focused
|
|
@@ -1080,46 +1299,7 @@ window.__ModuleLoader__.load({
|
|
|
1080
1299
|
if (open) {
|
|
1081
1300
|
sessions.forEach((row) => {
|
|
1082
1301
|
index += 1
|
|
1083
|
-
|
|
1084
|
-
const rowHovered = hover === 'session:' + row.id
|
|
1085
|
-
const lifted = dropping === 'session:' + row.id
|
|
1086
|
-
children.push(React.createElement('div', {
|
|
1087
|
-
key: 'session:' + row.id,
|
|
1088
|
-
'data-polish-ws-session': row.id,
|
|
1089
|
-
draggable: true,
|
|
1090
|
-
style: Object.assign({}, WS_ROW, {
|
|
1091
|
-
paddingLeft: '24px',
|
|
1092
|
-
opacity: drag && drag.kind === 'session' && drag.id === row.id ? 0.5 : undefined,
|
|
1093
|
-
background: lifted
|
|
1094
|
-
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
1095
|
-
: (rowFocused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
1096
|
-
boxShadow: rowFocused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
1097
|
-
}),
|
|
1098
|
-
onClick: () => services.openSession(row.id),
|
|
1099
|
-
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
1100
|
-
onMouseEnter: () => setHover('session:' + row.id),
|
|
1101
|
-
onMouseLeave: () => setHover(null),
|
|
1102
|
-
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
1103
|
-
onDragOver: (event) => {
|
|
1104
|
-
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
1105
|
-
event.preventDefault()
|
|
1106
|
-
setDropping('session:' + row.id)
|
|
1107
|
-
},
|
|
1108
|
-
onDragLeave: () => setDropping(null),
|
|
1109
|
-
onDrop: (event) => { event.preventDefault(); dropOnSession(workspace.workspaceId, row.id) },
|
|
1110
|
-
},
|
|
1111
|
-
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
1112
|
-
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
1113
|
-
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
1114
|
-
rowHovered || rowFocused
|
|
1115
|
-
? React.createElement('button', {
|
|
1116
|
-
type: 'button',
|
|
1117
|
-
'data-polish-ws-menu': 'session',
|
|
1118
|
-
'aria-label': 'Session menu',
|
|
1119
|
-
style: WS_ROW_ACTION,
|
|
1120
|
-
onClick: (event) => openMenu(event, row, workspace),
|
|
1121
|
-
}, React.createElement(DotsGlyph))
|
|
1122
|
-
: null))
|
|
1302
|
+
children.push(sessionRow(workspace, row, index))
|
|
1123
1303
|
})
|
|
1124
1304
|
}
|
|
1125
1305
|
})
|
|
@@ -1272,6 +1452,7 @@ window.__ModuleLoader__.load({
|
|
|
1272
1452
|
reorderWorkspace: (movingId, beforeId) => seated.workspaces.insertBefore(movingId, beforeId),
|
|
1273
1453
|
reorderSession: (workspaceId, movingId, beforeId) => seated.workspaces.insertSessionBefore(workspaceId, movingId, beforeId),
|
|
1274
1454
|
removeWorkspace: (id) => seated.workspaces.delete(id),
|
|
1455
|
+
renameWorkspace: (id, title) => seated.workspaces.rename(id, title),
|
|
1275
1456
|
openFolder: async (path) => {
|
|
1276
1457
|
const answer = await seated.remote.session.openWorkspacePath({ path })
|
|
1277
1458
|
if (answer && answer.ok === false) throw new Error('cannot open the folder')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
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",
|