@ssebastien/tcmd 0.1.3 → 0.1.7

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.
Files changed (134) hide show
  1. package/README.md +256 -62
  2. package/bin/tcmd +10 -2
  3. package/bin/tcmd.js +13 -2
  4. package/desktop/dist/index.html +13 -0
  5. package/desktop/dist/main.js +20298 -0
  6. package/desktop/dist/style.css +1515 -0
  7. package/desktop/frontend/index.html +13 -0
  8. package/package.json +19 -6
  9. package/public/fonts/OFL.txt +93 -0
  10. package/public/index.html +14 -0
  11. package/public/licenses/LUCIDE.txt +43 -0
  12. package/public/main.js +20679 -0
  13. package/public/style.css +1515 -0
  14. package/scripts/build-desktop.js +31 -0
  15. package/scripts/serve-web.js +61 -0
  16. package/src/js/tcmd/components/App.html +80 -14
  17. package/src/js/tcmd/components/App.js +339 -114
  18. package/src/js/tcmd/components/App.xml +76 -22
  19. package/src/js/tcmd/components/BookmarkList.html +5 -5
  20. package/src/js/tcmd/components/BookmarkList.js +15 -41
  21. package/src/js/tcmd/components/BookmarkList.xml +1 -2
  22. package/src/js/tcmd/components/BookmarkRow.html +2 -1
  23. package/src/js/tcmd/components/BookmarkRow.js +14 -9
  24. package/src/js/tcmd/components/BookmarkRow.xml +1 -1
  25. package/src/js/tcmd/components/Breadcrumbs.js +1 -1
  26. package/src/js/tcmd/components/CommandArgRow.html +28 -0
  27. package/src/js/tcmd/components/CommandArgRow.js +146 -30
  28. package/src/js/tcmd/components/CommandArgRow.xml +55 -4
  29. package/src/js/tcmd/components/CommandBar.html +2 -2
  30. package/src/js/tcmd/components/CommandBar.js +33 -27
  31. package/src/js/tcmd/components/CommandBar.xml +2 -2
  32. package/src/js/tcmd/components/CommandItem.html +1 -1
  33. package/src/js/tcmd/components/CommandItem.js +25 -25
  34. package/src/js/tcmd/components/CommandItem.xml +2 -2
  35. package/src/js/tcmd/components/CommandPanel.html +29 -0
  36. package/src/js/tcmd/components/CommandPanel.js +78 -52
  37. package/src/js/tcmd/components/CommandPanel.xml +49 -25
  38. package/src/js/tcmd/components/Confirm.html +2 -2
  39. package/src/js/tcmd/components/EntryDetail.html +7 -7
  40. package/src/js/tcmd/components/EntryDetail.js +22 -8
  41. package/src/js/tcmd/components/FileList.html +5 -1
  42. package/src/js/tcmd/components/FileList.js +2 -1
  43. package/src/js/tcmd/components/FilePicker.html +10 -0
  44. package/src/js/tcmd/components/FilePicker.js +43 -0
  45. package/src/js/tcmd/components/FilePicker.xml +13 -0
  46. package/src/js/tcmd/components/FilePickerRow.html +6 -0
  47. package/src/js/tcmd/components/FilePickerRow.js +27 -0
  48. package/src/js/tcmd/components/FilePickerRow.xml +10 -0
  49. package/src/js/tcmd/components/FilePreview.html +14 -4
  50. package/src/js/tcmd/components/FilePreview.js +111 -12
  51. package/src/js/tcmd/components/FilePreview.xml +28 -8
  52. package/src/js/tcmd/components/FileRow.html +2 -1
  53. package/src/js/tcmd/components/FileRow.js +70 -12
  54. package/src/js/tcmd/components/FilterBar.html +1 -1
  55. package/src/js/tcmd/components/FilterBar.js +14 -4
  56. package/src/js/tcmd/components/FilterBar.xml +1 -1
  57. package/src/js/tcmd/components/Frame.js +324 -0
  58. package/src/js/tcmd/components/Help.html +15 -0
  59. package/src/js/tcmd/components/Help.js +260 -0
  60. package/src/js/tcmd/components/Help.xml +13 -4
  61. package/src/js/tcmd/components/HelpRow.html +8 -0
  62. package/src/js/tcmd/components/HelpRow.xml +7 -0
  63. package/src/js/tcmd/components/OverlayList.html +8 -0
  64. package/src/js/tcmd/components/OverlayList.js +69 -0
  65. package/src/js/tcmd/components/{CommandPalette.xml → OverlayList.xml} +2 -5
  66. package/src/js/tcmd/components/OverlayRow.html +5 -0
  67. package/src/js/tcmd/components/OverlayRow.js +47 -0
  68. package/src/js/tcmd/components/OverlayRow.xml +9 -0
  69. package/src/js/tcmd/components/Summary.html +1 -1
  70. package/src/js/tcmd/components/Summary.js +1 -6
  71. package/src/js/tcmd/components/format.js +4 -23
  72. package/src/js/tcmd/components/icons.js +176 -5
  73. package/src/js/tcmd/components/index.js +9 -5
  74. package/src/js/tcmd/components/rows.js +176 -48
  75. package/src/js/tcmd/core/base64.js +64 -0
  76. package/src/js/tcmd/core/bookmarks.js +45 -8
  77. package/src/js/tcmd/core/commands.js +978 -260
  78. package/src/js/tcmd/core/concurrency.js +59 -0
  79. package/src/js/tcmd/core/decompress.js +117 -0
  80. package/src/js/tcmd/core/directory.js +219 -93
  81. package/src/js/tcmd/core/format.js +36 -0
  82. package/src/js/tcmd/core/fs.js +438 -54
  83. package/src/js/tcmd/core/history.js +58 -0
  84. package/src/js/tcmd/core/index.js +3 -9
  85. package/src/js/tcmd/core/journal.js +93 -0
  86. package/src/js/tcmd/core/keymap.js +42 -6
  87. package/src/js/tcmd/core/operations.js +813 -48
  88. package/src/js/tcmd/core/os.js +29 -3
  89. package/src/js/tcmd/core/pane.js +33 -8
  90. package/src/js/tcmd/core/panel.js +280 -104
  91. package/src/js/tcmd/core/path.js +30 -3
  92. package/src/js/tcmd/core/permissions.js +107 -0
  93. package/src/js/tcmd/core/picker.js +156 -0
  94. package/src/js/tcmd/core/preview.js +102 -0
  95. package/src/js/tcmd/core/script.js +7 -95
  96. package/src/js/tcmd/core/selection.js +9 -1
  97. package/src/js/tcmd/core/session.js +498 -200
  98. package/src/js/tcmd/core/watch.js +153 -0
  99. package/src/js/tcmd/desktop/backend/api.js +165 -0
  100. package/src/js/tcmd/desktop/backend/fs.js +626 -0
  101. package/src/js/tcmd/desktop/backend/main.ts +9 -0
  102. package/src/js/tcmd/desktop/bookmarks.js +23 -0
  103. package/src/js/tcmd/desktop/fs.js +91 -0
  104. package/src/js/tcmd/desktop/main.js +194 -0
  105. package/src/js/tcmd/desktop/os.js +33 -0
  106. package/src/js/tcmd/index.js +0 -1
  107. package/src/js/tcmd/tui/app.js +118 -60
  108. package/src/js/tcmd/tui/catalogue.js +238 -42
  109. package/src/js/tcmd/tui/index.js +18 -19
  110. package/src/js/tcmd/tui/opentui.js +33 -0
  111. package/src/js/tcmd/tui/syntax.js +91 -0
  112. package/src/js/tcmd/tui/theme.js +32 -2
  113. package/src/js/tcmd/vendor/bzip2.js +276 -0
  114. package/src/js/tcmd/web/app.js +225 -41
  115. package/src/js/tcmd/web/bookmarks.js +36 -0
  116. package/src/js/tcmd/web/catalogue.js +81 -38
  117. package/src/js/tcmd/web/effects.js +223 -0
  118. package/src/js/tcmd/web/header.html +1 -1
  119. package/src/js/tcmd/web/header.js +15 -11
  120. package/src/js/tcmd/web/http.js +13 -0
  121. package/src/js/tcmd/web/index.js +2 -2
  122. package/src/js/tcmd/web/main.js +6 -66
  123. package/src/js/tcmd/web/opfs.js +297 -30
  124. package/src/js/tcmd/web/seed.js +4 -4
  125. package/src/js/tcmd/web/server.js +124 -21
  126. package/src/js/tcmd/web/theme.js +4 -0
  127. package/tinyjs.json +16 -0
  128. package/src/js/tcmd/components/CommandPalette.html +0 -7
  129. package/src/js/tcmd/components/CommandPalette.js +0 -58
  130. package/src/js/tcmd/components/PaletteRow.html +0 -3
  131. package/src/js/tcmd/components/PaletteRow.js +0 -43
  132. package/src/js/tcmd/components/PaletteRow.xml +0 -4
  133. package/src/js/tcmd/tui/frame.js +0 -269
  134. package/src/js/tcmd/tui/help.js +0 -32
@@ -10,7 +10,7 @@
10
10
  <!-- Identity bar: the one visible trace of TinyCmd itself (the host
11
11
  aside, everything else on screen is the user's files). "Tiny"
12
12
  shimmers blue letter by letter, "Cmd" shimmers orange. -->
13
- <box flexDirection="row" height="1" paddingLeft="1" overflow="hidden">
13
+ <box flexDirection="row" height="1" paddingLeft="1" paddingRight="1" overflow="hidden">
14
14
  <text out="brandT" out:fg="brandTFg" wrapMode="none"></text>
15
15
  <text out="brandI" out:fg="brandIFg" wrapMode="none"></text>
16
16
  <text out="brandN" out:fg="brandNFg" wrapMode="none"></text>
@@ -19,6 +19,26 @@
19
19
  <text out="brandM" out:fg="brandMFg" wrapMode="none"></text>
20
20
  <text out="brandD" out:fg="brandDFg" wrapMode="none"></text>
21
21
  <text out="topBar" out:fg="topBarFg" wrapMode="none"></text>
22
+ <box flexGrow="1" flexShrink="1" minWidth="0" overflow="hidden"></box>
23
+ <!-- The view mode, letter by letter so each carries its gradient:
24
+ explorer shimmers along the Tiny blues, commander along the
25
+ Cmd oranges (see `modeGradient` in Frame.js). The trailing
26
+ slots of the shorter word are empty and take no column. -->
27
+ <box flexDirection="row" flexShrink="0" overflow="hidden">
28
+ <!-- Clicking the mode word flips explorer ⇄ commander. -->
29
+ <box flexDirection="row" overflow="hidden" on:click="!view:toggleMode">
30
+ <text when="modeHas0" out="modeL0" out:fg="modeFg0" wrapMode="none"></text>
31
+ <text when="modeHas1" out="modeL1" out:fg="modeFg1" wrapMode="none"></text>
32
+ <text when="modeHas2" out="modeL2" out:fg="modeFg2" wrapMode="none"></text>
33
+ <text when="modeHas3" out="modeL3" out:fg="modeFg3" wrapMode="none"></text>
34
+ <text when="modeHas4" out="modeL4" out:fg="modeFg4" wrapMode="none"></text>
35
+ <text when="modeHas5" out="modeL5" out:fg="modeFg5" wrapMode="none"></text>
36
+ <text when="modeHas6" out="modeL6" out:fg="modeFg6" wrapMode="none"></text>
37
+ <text when="modeHas7" out="modeL7" out:fg="modeFg7" wrapMode="none"></text>
38
+ <text when="modeHas8" out="modeL8" out:fg="modeFg8" wrapMode="none"></text>
39
+ </box>
40
+ <text out="topBarStatus" out:fg="topBarStatusFg" wrapMode="none"></text>
41
+ </box>
22
42
  </box>
23
43
 
24
44
  <!-- Minimum-size gate: below the breakpoint the workspace is unusable. -->
@@ -27,72 +47,106 @@
27
47
  <text out="minSizeHint" out:fg="dimFg"></text>
28
48
  </box>
29
49
 
30
- <!-- Two boxed panes plus the inline inspector. -->
50
+ <!-- Columns: commander tiles source and destination; explorer leads
51
+ with the parent mirror so the current pane sits in the middle,
52
+ slightly wider than its neighbours. The inspector completes the
53
+ three columns in either mode. -->
31
54
  <box when="!tooSmall" flexDirection="column" flexGrow="1" overflow="hidden">
32
- <box flexDirection="row" flexGrow="1" overflow="hidden">
33
- <box flexDirection="column" flexGrow="1" border borderStyle="single"
55
+ <box when="!showPreviewFull" flexDirection="row" flexGrow="1" overflow="hidden">
56
+ <box when="showParentPane" flexDirection="column" flexGrow="1" flexBasis="0" border borderStyle="single"
57
+ out:border="leftBorder" out:borderColor="parentBorderFg"
58
+ on:click="parentId!pane:activate">
59
+ <box flexDirection="row" height="1" paddingLeft="1" overflow="hidden">
60
+ <box when="!parentFilterOn" flexGrow="1" flexShrink="1" minWidth="0" overflow="hidden">
61
+ <breadcrumbs in:segments="parentSegments" in:paneId="parentId" height="1"></breadcrumbs>
62
+ </box>
63
+ <filterbar in:query="parentFilter" in:paneId="parentId" in:filtering="filtering" in:entries="parentEntries" in:setFilter="parentSetFilter"></filterbar>
64
+ </box>
65
+ <box height="1" out:border="headRule" out:borderColor="parentHeadRuleFg"></box>
66
+ <filelist in:entries="parentEntries" in:cursor="parentCursor" in:marked="parentMarked"
67
+ in:active="parentActive" in:paneId="parentId" in:loading="parentLoading"
68
+ in:viewport="listViewport"></filelist>
69
+ <entrydetail in:entry="parentEntry" in:marked="parentMarked" height="2"></entrydetail>
70
+ </box>
71
+ <box when="showLeftPane" flexDirection="column" flexBasis="0" out:flexGrow="leftGrow" border borderStyle="single"
34
72
  out:border="leftBorder" out:borderColor="leftBorderFg"
35
73
  on:click="leftId!pane:activate">
36
74
  <box flexDirection="row" height="1" paddingLeft="1" overflow="hidden">
37
75
  <box when="!leftFilterOn" flexGrow="1" flexShrink="1" minWidth="0" overflow="hidden">
38
76
  <breadcrumbs in:segments="leftSegments" in:paneId="leftId" height="1"></breadcrumbs>
39
77
  </box>
40
- <filterbar in:query="leftFilter" in:paneId="leftId" in:filtering="filtering" in:entries="leftEntries"></filterbar>
78
+ <filterbar in:query="leftFilter" in:paneId="leftId" in:filtering="filtering" in:entries="leftEntries" in:setFilter="leftSetFilter"></filterbar>
41
79
  </box>
42
80
  <box height="1" out:border="headRule" out:borderColor="leftHeadRuleFg"></box>
43
81
  <filelist in:entries="leftEntries" in:cursor="leftCursor" in:marked="leftMarked"
44
82
  in:active="leftActive" in:paneId="leftId" in:loading="leftLoading"
45
83
  in:viewport="listViewport"></filelist>
46
84
  <entrydetail in:entry="leftEntry" in:marked="leftMarked" height="2"></entrydetail>
47
- <summary in:entries="leftEntries" in:disk="leftDisk" height="1"></summary>
48
85
  </box>
49
- <box flexDirection="column" flexGrow="1" border borderStyle="single"
86
+ <box when="showRightPane" flexDirection="column" flexBasis="0" out:flexGrow="rightGrow" border borderStyle="single"
50
87
  out:border="rightBorder" out:borderColor="rightBorderFg"
51
88
  on:click="rightId!pane:activate">
52
89
  <box flexDirection="row" height="1" paddingLeft="1" overflow="hidden">
53
90
  <box when="!rightFilterOn" flexGrow="1" flexShrink="1" minWidth="0" overflow="hidden">
54
91
  <breadcrumbs in:segments="rightSegments" in:paneId="rightId" height="1"></breadcrumbs>
55
92
  </box>
56
- <filterbar in:query="rightFilter" in:paneId="rightId" in:filtering="filtering" in:entries="rightEntries"></filterbar>
93
+ <filterbar in:query="rightFilter" in:paneId="rightId" in:filtering="filtering" in:entries="rightEntries" in:setFilter="rightSetFilter"></filterbar>
57
94
  </box>
58
95
  <box height="1" out:border="headRule" out:borderColor="rightHeadRuleFg"></box>
59
96
  <filelist in:entries="rightEntries" in:cursor="rightCursor" in:marked="rightMarked"
60
97
  in:active="rightActive" in:paneId="rightId" in:loading="rightLoading"
61
98
  in:viewport="listViewport"></filelist>
62
99
  <entrydetail in:entry="rightEntry" in:marked="rightMarked" height="2"></entrydetail>
63
- <summary in:entries="rightEntries" in:disk="rightDisk" height="1"></summary>
64
100
  </box>
65
101
  <filepreview when="showInspectorInline" in:preview="preview" in:show="showInspectorInline"
66
- in:entry="appEntry" flexGrow="1"></filepreview>
102
+ in:entry="appEntry" in:paneId="previewId" in:scroll="previewScroll"
103
+ in:dirSegments="previewSegments" in:dirEntries="previewEntries"
104
+ in:dirCursor="previewCursor" in:dirMarked="previewMarked"
105
+ in:dirActive="previewActive" in:dirLoading="previewLoading"
106
+ flexGrow="1" flexBasis="0"></filepreview>
67
107
  </box>
108
+ <!-- Maximized inspector (`⇧V`): the preview takes the whole pane
109
+ row, the identity and command bars staying in place. -->
110
+ <filepreview when="showPreviewFull" in:preview="preview" in:show="showPreviewFull"
111
+ in:entry="appEntry" in:paneId="previewId" in:scroll="previewScroll"
112
+ in:dirSegments="previewSegments" in:dirEntries="previewEntries"
113
+ in:dirCursor="previewCursor" in:dirMarked="previewMarked"
114
+ in:dirActive="previewActive" in:dirLoading="previewLoading"
115
+ flexGrow="1"></filepreview>
68
116
  <commandbar in:items="commandBarItems" in:notice="notice" in:leftError="leftError"
69
- in:rightError="rightError" in:active="active"
117
+ in:rightError="rightError" in:active="active" in:uiSize="uiSize"
70
118
  in:leftMarked="leftMarked" in:rightMarked="rightMarked" out:height="barHeight"></commandbar>
71
119
  </box>
72
120
  </box>
73
121
 
74
- <!-- Fullscreen inspector for narrow viewports. -->
122
+ <!-- Fullscreen inspector overlay: `v` below the inline breakpoint, or
123
+ Enter on a file the host cannot edit (any width). -->
75
124
  <box when="showViewer" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="10"
76
125
  alignItems="center" justifyContent="center">
77
126
  <box width="80%" height="90%">
78
- <filepreview in:preview="preview" in:show="showViewer" in:entry="appEntry"></filepreview>
127
+ <filepreview in:preview="preview" in:show="showViewer" in:entry="appEntry" in:paneId="previewId"
128
+ in:scroll="previewScroll"
129
+ in:dirSegments="previewSegments" in:dirEntries="previewEntries"
130
+ in:dirCursor="previewCursor" in:dirMarked="previewMarked"
131
+ in:dirActive="previewActive" in:dirLoading="previewLoading"></filepreview>
79
132
  </box>
80
133
  </box>
81
134
 
82
- <commandpalette when="paletteOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="20"
83
- in:open="paletteOpen" in:mode="paletteMode" in:query="paletteQuery"
84
- in:items="paletteItems" in:selected="paletteIndex"></commandpalette>
85
135
  <commandpanel when="panelOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="22"
86
- in:open="panelOpen" in:operation="panelOperation" in:items="panelItems"
136
+ in:open="panelOpen" in:entry="panelEntry" in:items="panelItems"
87
137
  in:opCursor="panelOpIndex" in:rows="panelRows" in:argCursor="panelRowIndex"
88
- in:focus="panelFocus" in:textual="panelTextual" in:argRow="panelCursorRow"
89
- in:command="panelCommand" in:runnable="panelRunnable"
138
+ in:permBit="panelPermBit" in:focus="panelFocus"
139
+ in:preview="panelPreview"
140
+ in:picking="pickerOpen"
141
+ in:pickerPathText="pickerPathText" in:pickerFocus="pickerFocus" in:pickerRows="pickerRows"
90
142
  in:query="panelQuery" in:value="panelValue"></commandpanel>
91
143
  <bookmarklist when="bookmarkOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="25"
92
- in:open="bookmarkOpen" in:mode="bookmarkMode" in:query="bookmarkQuery"
93
- in:target="bookmarkTarget" in:items="bookmarkItems" in:selected="bookmarkIndex"></bookmarklist>
144
+ in:open="bookmarkOpen" in:query="bookmarkQuery"
145
+ in:items="bookmarkItems" in:selected="bookmarkIndex"></bookmarklist>
146
+ <overlaylist when="overlayOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="27"
147
+ in:open="overlay" in:mode="overlay" in:items="overlayItems" in:cursor="overlayIndex"></overlaylist>
94
148
  <confirm when="confirm" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="30"
95
149
  in:confirm="confirm"></confirm>
96
150
  <help when="helpOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="40"
97
- in:open="helpOpen"></help>
151
+ in:open="helpOpen" in:size="uiSize"></help>
98
152
  </box>
@@ -1,12 +1,12 @@
1
- <div class="bookmark-list" when="open">
2
- <div class="bookmark-dialog">
1
+ <div class="bookmark-list" when="open" role="dialog" aria-modal="true" aria-label="Bookmarks" on:click="!dialog:bookmark-cancel">
2
+ <div class="bookmark-dialog" on:click="dialogStop">
3
+ <h2 out="title"></h2>
3
4
  <header>
4
5
  <span class="bookmark-sign" out="sign"></span>
5
- <input class="bookmark-query" type="text" inout="query" on:enter="confirm">
6
+ <input class="bookmark-query" type="text" inout="query" aria-label="Filter bookmarks" on:enter="confirm">
6
7
  </header>
7
- <p class="bookmark-target" when="inputVisible" out="target"></p>
8
8
  <p class="bookmark-empty" when="emptyList" out="empty"></p>
9
- <ul class="bookmark-rows" when="listVisible" out="rows"></ul>
9
+ <ul class="bookmark-rows" out="rows"></ul>
10
10
  <footer out="footer"></footer>
11
11
  </div>
12
12
  </div>
@@ -4,65 +4,39 @@
4
4
  // Created: 2026-10-09
5
5
 
6
6
  // Module: tcmd/components/BookmarkList
7
- // BookmarkList: the `g`/`b` overlay. In "name" mode it shows an input to name
8
- // the path being recorded; in "list" mode it shows the fuzzy picker over the
9
- // stored bookmarks (matched by name or path). The row logic lives in
7
+ // BookmarkList: the single bookmarks panel opened with `b`. The input filters
8
+ // the stored bookmarks (matched by name or path); the first row offers to add
9
+ // the current path when it is not bookmarked yet. The row logic lives in
10
10
  // `BookmarkRow.js`; this parent maps its `items` to rows.
11
11
  //
12
12
  // Environment: `overlayBg`, `borderPrimaryActiveFg`, `textSecondaryFg`
13
- // (footer/path), `textHeadingFg` (title), `rows(data)` returning the keyed rows
14
- // and `scrollTop(self, data)` (TUI viewport tracking).
13
+ // (footer), `textHeadingFg` (title), `rows(data)` returning the keyed rows and
14
+ // `scrollTop(self, data)` (TUI viewport tracking).
15
15
 
16
16
  import { unwrap } from "select/state/cells"
17
17
 
18
- // Function: title
19
- // The overlay title: "bookmark" while naming, "bookmarks" while picking.
20
- function bookmarkTitle(mode) {
21
- return unwrap(mode) === "name" ? "bookmark" : "bookmarks"
22
- }
23
-
24
- // Function: footer
25
- function bookmarkFooter(mode) {
26
- return unwrap(mode) === "name"
27
- ? "name the bookmark ↵ save Esc cancel"
28
- : "↑↓ move ↵ jump Esc close"
29
- }
30
-
31
- // Function: targetLine
32
- // The path being recorded (name mode). `target` is `{ path, isDir }` or null.
33
- function targetLine(target) {
34
- const value = unwrap(target)
35
- if (!value) return ""
36
- if (typeof value === "string") return value
37
- return `${value.path ?? ""}${value.isDir === true ? "/" : ""}`
38
- }
39
-
40
18
  // Function: createBehaviors
41
19
  // Builds the BookmarkList behavior map for the given renderer environment.
42
20
  function createBehaviors(env) {
43
21
  const behaviors = {
44
22
  bg: () => env.overlayBg,
45
23
  borderFg: () => env.borderPrimaryActiveFg,
46
- confirm: (self, data) => self.pub("bookmark:confirm"),
47
- footer: (_self, data) => bookmarkFooter(data.mode),
24
+ confirm: (self) => self.pub("bookmark:confirm"),
25
+ footer: () => "↑↓ move ⌫/Del remove ↵ open Esc close",
48
26
  footerFg: () => env.textSecondaryFg,
49
- // The input is focused/typing in name mode; the list navigates otherwise.
27
+ // The filter input owns typing; the list navigates with the arrows.
50
28
  focused: (_self, data) => Boolean(unwrap(data.open)),
51
- inputVisible: (_self, data) => unwrap(data.mode) === "name",
52
- listVisible: (_self, data) => unwrap(data.mode) === "list",
53
29
  open: (_self, data) => Boolean(unwrap(data.open)),
54
- pathFg: () => env.textSecondaryFg,
55
- sign: (_self, data) => (unwrap(data.mode) === "name" ? "+" : "›"),
56
- target: (_self, data) => targetLine(data.target),
57
- title: (_self, data) => bookmarkTitle(data.mode),
30
+ sign: () => "›",
31
+ title: () => "bookmarks",
58
32
  titleColor: () => env.textHeadingFg,
59
- emptyList: (_self, data) =>
60
- unwrap(data.mode) === "list" && (unwrap(data.items)?.length ?? 0) === 0,
61
- empty: () => " no bookmarks",
33
+ emptyList: (_self, data) => (unwrap(data.items)?.length ?? 0) === 0,
34
+ empty: (_self, data) =>
35
+ `${unwrap(data.query) ?? ""}`.trim() ? " no matching bookmarks" : " no bookmarks",
62
36
  emptyFg: () => env.textSecondaryFg,
63
37
  }
64
38
  if (env?.rows) {
65
- behaviors.rows = (_self, data) => env.rows(self, data)
39
+ behaviors.rows = (self, data) => env.rows(self, data)
66
40
  }
67
41
  if (env?.scrollTop) {
68
42
  behaviors.scrollTop = (self, data) => env.scrollTop(self, data)
@@ -70,4 +44,4 @@ function createBehaviors(env) {
70
44
  return behaviors
71
45
  }
72
46
 
73
- export { bookmarkTitle, createBehaviors }
47
+ export { createBehaviors }
@@ -6,9 +6,8 @@
6
6
  <text out="sign" out:fg="titleColor"></text>
7
7
  <input ref="query" inout="query" flexGrow="1" height="1" out:focused="focused" on:enter="confirm"></input>
8
8
  </box>
9
- <text when="inputVisible" out="target" out:fg="pathFg" paddingLeft="1" wrapMode="none" truncate="true"></text>
10
9
  <text when="emptyList" out="empty" out:fg="emptyFg" paddingLeft="1" wrapMode="none"></text>
11
- <scrollbox when="listVisible" ref="list" flexGrow="1">
10
+ <scrollbox ref="list" flexGrow="1">
12
11
  <box out="rows" flexDirection="column"></box>
13
12
  </scrollbox>
14
13
  <box flexDirection="row" height="1" paddingLeft="1">
@@ -1,5 +1,6 @@
1
- <li class="bookmark-row" out:class="rowClass" on:click="item.name!bookmark:select">
1
+ <li class="bookmark-row" out:class="rowClass" on:click="item.key!bookmark:select">
2
2
  <span class="bookmark-marker" out="rowMarker"></span>
3
+ <svg class="bookmark-icon ui-icon" when="hasIcon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use out:href="rowIconHref"></use></svg>
3
4
  <span class="bookmark-name" out="rowName"></span>
4
5
  <span class="bookmark-path" out="rowPath"></span>
5
6
  </li>
@@ -4,17 +4,18 @@
4
4
  // Created: 2026-10-09
5
5
 
6
6
  // Module: tcmd/components/BookmarkRow
7
- // One row of the bookmark picker. Reads `{ item: { name, path }, index,
8
- // selected }`, the same bag shape in both engines. Selection is snapshotted by
9
- // the row builder into a plain `selectedRow` boolean and read through the
10
- // shared `isSelected` predicate, with the `selected` cell as fallback for
11
- // hand-built bags.
7
+ // One row of the bookmarks panel. Reads `{ item: { name, path, add? }, index,
8
+ // selected }`, the same bag shape in both engines. The synthetic add row reads
9
+ // as an action; stored rows read as `name/` for directories. Selection is
10
+ // snapshotted by the row builder into a plain `selectedRow` boolean and read
11
+ // through the shared `isSelected` predicate, with the `selected` cell as
12
+ // fallback for hand-built bags.
12
13
  //
13
14
  // Environment: `accentFg`, `textFg`, `textSecondaryFg`, `selectionBg` (TUI row
14
15
  // background), `selectedClass` (web), `icons` (show Nerd Font icons).
15
16
 
16
17
  import { isSelected } from "./rows.js"
17
- import { iconFor } from "./icons.js"
18
+ import { iconFor, lucideIconFor } from "./icons.js"
18
19
 
19
20
  // Function: createBehaviors
20
21
  // Builds the BookmarkRow behavior map for the given renderer environment.
@@ -25,13 +26,17 @@ function createBehaviors(env) {
25
26
  rowFg: (_self, data) => (isSelected(data) ? env.accentFg : env.textFg),
26
27
  // The icon is its own slot, dimmer than the name; it collapses when
27
28
  // the environment keeps rows icon-free.
28
- hasIcon: () => Boolean(env.icons),
29
+ hasIcon: () => Boolean(env.icons || env.lucideIcons),
29
30
  rowIcon: (_self, data) => (env.icons ? iconFor(data.item) : ""),
31
+ rowIconHref: (_self, data) => (env.lucideIcons ? `#lucide-${lucideIconFor(data.item)}` : ""),
30
32
  rowIconFg: () => env.textSecondaryFg,
31
33
  rowMarker: (_self, data) => (isSelected(data) ? ">" : " "),
32
- // A directory bookmark reads as `name/`, mirroring the listing.
34
+ // The add row names its action; a directory bookmark reads as
35
+ // `name/`, mirroring the listing.
33
36
  rowName: (_self, data) =>
34
- `${data.item?.name ?? ""}${data.item?.isDir === true ? "/" : ""}`,
37
+ data.item?.add
38
+ ? data.item.name ?? ""
39
+ : `${data.item?.name ?? ""}${data.item?.isDir === true ? "/" : ""}`,
35
40
  rowPath: (_self, data) => `${data.item?.path ?? ""}`,
36
41
  rowPathFg: () => env.textSecondaryFg,
37
42
  }
@@ -1,4 +1,4 @@
1
- <box flexDirection="row" height="1" columnGap="1" out:backgroundColor="rowBg" on:click="item.name!bookmark:select">
1
+ <box flexDirection="row" height="1" columnGap="1" out:backgroundColor="rowBg" on:click="item.key!bookmark:select">
2
2
  <text out="rowMarker" width="2" out:fg="rowFg"></text>
3
3
  <text out="rowIcon" out:fg="rowIconFg" wrapMode="none" when="hasIcon"></text>
4
4
  <box minWidth="12" flexShrink="0">
@@ -17,7 +17,7 @@
17
17
  function createBehaviors(env) {
18
18
  const behaviors = { listClass: () => env?.listClass }
19
19
  if (env?.rows) {
20
- behaviors.rows = (_self, data) => env.rows(self, data)
20
+ behaviors.rows = (self, data) => env.rows(self, data)
21
21
  }
22
22
  return behaviors
23
23
  }
@@ -0,0 +1,28 @@
1
+ <li class="panel-row" out:class="rowClass" out:data-risk="row.risk" out:data-group="row.group" out:data-type="row.type" out:data-selected="rowSelected" on:click="row.key!panel:select">
2
+ <span class="panel-list-indent" when="isList" out="listSpacer"></span>
3
+ <span class="panel-list-box" when="isList">
4
+ <span class="panel-marker" when="isItem" out="marker"></span>
5
+ <span class="panel-name" when="isItem" out="name"></span>
6
+ <span class="panel-summary" when="isItem" out="summary"></span>
7
+ </span>
8
+ <span class="panel-name panel-group-name" when="isGroup" out="name"></span>
9
+ <span class="panel-marker" when="showPlain" out="marker"></span>
10
+ <span class="panel-name" when="showPlain" out="name"></span>
11
+ <span class="panel-marker" when="isArg" out="marker"></span>
12
+ <span class="panel-name panel-arg-name" when="isArg" out="name"></span>
13
+ <span class="panel-perm" when="isPerm">
14
+ <span class="panel-perm-class" out="permClass"></span>
15
+ <button class="panel-perm-bit" type="button" tabindex="-1" out:class="permRClass" out:aria-pressed="permROn" on:click="row.keyR!panel:perm." out="permR"></button>
16
+ <button class="panel-perm-bit" type="button" tabindex="-1" out:class="permWClass" out:aria-pressed="permWOn" on:click="row.keyW!panel:perm." out="permW"></button>
17
+ <button class="panel-perm-bit" type="button" tabindex="-1" out:class="permXClass" out:aria-pressed="permXOn" on:click="row.keyX!panel:perm." out="permX"></button>
18
+ </span>
19
+ <button class="panel-run-button" when="isRun" type="button" tabindex="-1" out:aria-label="runAriaLabel" out="runLabel"></button>
20
+ <span class="panel-field" when="isArg" out:data-editable="editable">
21
+ <span class="panel-field-bracket" out="fieldOpen"></span>
22
+ <input class="panel-value" when="editing" type="text" inout="value" aria-label="Argument value" />
23
+ <span class="panel-field-value" when="!editing" out="summary"></span>
24
+ <span class="panel-field-bracket" out="fieldClose"></span>
25
+ </span>
26
+ <span class="panel-summary" when="isPlain" out="summary"></span>
27
+ <svg class="panel-op-chevron ui-icon" when="isOp" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-chevron-right"></use></svg>
28
+ </li>