@leftoff/cli 0.9.9-beta.5 → 0.9.9-beta.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.
package/ui/start.js CHANGED
@@ -122,6 +122,12 @@ export function startPageHtml(data, ui = {}, { esc, icon, emoji = () => '', loca
122
122
  export function startBrowseHtml(b, { esc, icon = () => '', machine = '' } = {}) {
123
123
  let h = '<div class="start-browse" id="start-browse" role="region" aria-label="Folders on ' + esc(machine) + '">'
124
124
  const d = b && b.data
125
+ // or type one you know (~/src/project, or a full path): the browser goes there; the same rules as walking to it
126
+ const goTo = '<form class="sb-path" data-form="start-browse-path" novalidate><label class="sb-path-l" for="sb-path-in">Go to a folder</label><div class="sb-path-row">' +
127
+ '<input type="text" id="sb-path-in" class="sb-path-in" name="path" placeholder="~/code/web" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="go"' + (b && b.pathError ? ' aria-invalid="true" aria-describedby="sb-path-err"' : '') + '>' +
128
+ '<button type="submit" class="btn">Go</button></div>' +
129
+ (b && b.pathError ? '<p class="start-rule bad sb-path-err" id="sb-path-err" role="alert" data-st="path-err"></p>' : '') + '</form>'
130
+ if (!d) h += goTo
125
131
  if (d) {
126
132
  h += '<nav class="sb-crumbs" aria-label="Folder">' + d.crumbs.map((c, i) => (i ? '<span class="sb-sep" aria-hidden="true">/</span>' : '') +
127
133
  '<button type="button" class="sb-crumb" data-action="start-browse-go" data-crumb="' + i + '"' + (i === d.crumbs.length - 1 ? ' aria-current="location"' : '') + ' data-st="crumb-' + i + '"></button>').join('') + '</nav>'
@@ -129,6 +135,7 @@ export function startBrowseHtml(b, { esc, icon = () => '', machine = '' } = {})
129
135
  (d.parent ? '<button type="button" class="btn sb-up" data-action="start-browse-up" aria-label="Up one folder">' + icon('chevron-left') + 'Up</button>' : '') +
130
136
  '<span class="sb-cur"><span class="sb-cur-name" data-st="cur"></span>' + (d.git ? ' <span class="start-badge git">git</span>' : ' <span class="start-badge">not a git repo</span>') + '</span>' +
131
137
  '<button type="button" class="btn primary sb-use" data-action="start-browse-use"' + (d.parent ? '' : ' disabled') + '>Use this folder</button></div>'
138
+ h += goTo
132
139
  if (d.shortcuts && d.shortcuts.length > 1) h += '<div class="sb-cuts"><span class="sb-cuts-l">Jump to</span>' + d.shortcuts.map((c, i) => '<button type="button" class="sb-cut" data-action="start-browse-go" data-cut="' + i + '" data-st="cut-' + i + '"></button>').join('') + '</div>'
133
140
  if (!d.entries.length) h += '<p class="start-empty">No folders in here.</p>'
134
141
  else h += '<ul class="sb-list">' + d.entries.map((e, i) => '<li><button type="button" class="sb-dir" data-action="start-browse-go" data-entry="' + i + '">' + icon('folder') + '<span class="sb-name" data-st="entry-' + i + '"></span>' + (e.git ? '<span class="start-badge git">git</span>' : '') + icon('chevron-right', 'ic sb-go') + '</button></li>').join('') + '</ul>'
@@ -141,6 +148,17 @@ export function startBrowseHtml(b, { esc, icon = () => '', machine = '' } = {})
141
148
  return h
142
149
  }
143
150
 
151
+ // What you typed in "Go to a folder", as a path to ask for: no spaces around
152
+ // it, no quotes a copy brought along, a shell's "\ " back to a space, no
153
+ // trailing slash. "~" stays "~"; the Mac resolves it (src/dirs.js checkFolder).
154
+ export function typedPath(v) {
155
+ let t = String(v == null ? '' : v).trim()
156
+ if (/^(['"]).*\1$/.test(t) && t.length >= 2) t = t.slice(1, -1).trim()
157
+ t = t.replace(/\\ /g, ' ')
158
+ if (t.length > 1) t = t.replace(/\/+$/, '') || '/'
159
+ return t
160
+ }
161
+
144
162
  // The text for every [data-st] on the page: names and paths from a Mac's disk.
145
163
  export function startTexts(data, ui = {}) {
146
164
  const t = {}
@@ -153,6 +171,7 @@ export function startTexts(data, ui = {}) {
153
171
  ;(d.shortcuts || []).forEach((c, i) => { t['cut-' + i] = c.label })
154
172
  d.entries.forEach((e, i) => { t['entry-' + i] = e.name })
155
173
  }
174
+ if (ui.browse && ui.browse.pathError) t['path-err'] = ui.browse.pathError
156
175
  for (const a of (data && Array.isArray(data.pending) ? data.pending : [])) if (a.firstTime) t['ask-folder-' + a.id] = a.folder || ''
157
176
  return t
158
177
  }
@@ -189,6 +208,22 @@ export function startAsksBanner(info, { esc, icon, local = true, clock = (x) =>
189
208
  }).join('')
190
209
  }
191
210
 
211
+ // Asked once on THIS Mac's board when it pairs a phone or sees a new Mac:
212
+ // may it start sessions here? No is the default (first, and what doing
213
+ // nothing means); Yes turns on the switch, and Trust my paired devices if it's
214
+ // off (the words say so). Never shown off this Mac.
215
+ export function startPairAskHtml(info, { esc, local = true, busy = false } = {}) {
216
+ const a = info && info.pairAsk
217
+ if (!a || !local) return ''
218
+ const mac = info.machine || 'this Mac'
219
+ const who = a.kind === 'mac' ? (a.name || 'your other Mac') : 'your phone'
220
+ const what = a.kind === 'mac' ? 'Let ' + who + ' start sessions here?' : 'Let your phone start sessions here?'
221
+ const yes = 'Yes turns on “Allow starting sessions from other devices” for ' + mac + (info.trust ? '' : ', and Trust my paired devices so you can answer its prompts there') + '. It applies to every paired phone and Mac; change it any time in Settings → Privacy & security.'
222
+ return '<div class="banner info pair-ask" role="region" aria-label="' + esc(what) + '"><span class="grow"><b>' + esc(what) + '</b> ' + esc(a.kind === 'mac' ? who + ' could start Claude Code on ' + mac + ' with nobody here;' : 'It could start Claude Code on ' + mac + ' with nobody here;') + ' its edits and commands still wait for you in Needs you. ' + esc(yes) + '</span>' +
223
+ '<button type="button" class="btn" data-action="pair-ask-no"' + (busy ? ' aria-disabled="true"' : '') + '>No</button>' +
224
+ '<button type="button" class="btn" data-action="pair-ask-yes"' + (busy ? ' aria-disabled="true"' : '') + '>Yes, allow</button></div>'
225
+ }
226
+
192
227
  // The session page's line for a session Leftoff runs: where it runs, who
193
228
  // started it, and Stop / Resume. The error (Claude Code's own words) is put
194
229
  // in with textContent by app.js ([data-headless-err]).
@@ -209,8 +244,22 @@ export function headlessBarHtml(s, { esc, icon, may = true } = {}) {
209
244
  }
210
245
 
211
246
  // Settings → Privacy & security: the one switch, on this Mac only.
212
- export function startsSettingHtml(info, { esc, local = true } = {}) {
247
+ // 0.9.9: a setting kept only on one Mac (not in the synced config.json) wears a
248
+ // pill beside its label: "🖥️ studio only", with a styled tip. Synced settings
249
+ // get none, so the pill alone tells you which kind it is.
250
+ export const LOCAL_TIP = 'Kept on this Mac; doesn’t sync.'
251
+ export function localPillHtml({ machine, emoji = '', esc }) {
252
+ return ' <span class="local-pill" data-tip="' + esc(LOCAL_TIP) + '">' + (emoji ? '<span aria-hidden="true">' + esc(emoji) + '</span> ' : '') + esc(machine || 'this Mac') + ' only<span class="sr-only">: ' + esc(LOCAL_TIP) + '</span></span>'
253
+ }
254
+ // Away from that Mac the control is locked, and looks it: a lock, not just grey.
255
+ export function lockedAttrs(local) { return local ? '' : ' disabled aria-disabled="true"' }
256
+ export function lockHtml(local, icon) { return local ? '' : '<span class="lock" aria-hidden="true">' + (icon ? icon('lock') : '🔒') + '</span>' }
257
+
258
+ export function startsSettingHtml(info, { esc, local = true, emoji = '', icon = null } = {}) {
213
259
  if (!info) return ''
214
- return '<label for="f-start-allow">Start sessions</label><label class="check"><input id="f-start-allow" type="checkbox"' + (info.allowRemote ? ' checked' : '') + (local ? '' : ' disabled') + '><span>Allow starting sessions from other devices</span></label>' +
215
- '<div class="hint">Off: a session your phone or another Mac starts here waits until someone at this Mac confirms it. On: it starts right away, and this Mac shows a notification each time. Needs Trust my paired devices. Kept on this Mac only (it doesn’t sync)' + (local ? '.' : '; change it on ' + esc(info.machine || 'that Mac') + '.') + '</div>'
260
+ const m = esc(info.machine || 'that Mac')
261
+ return '<label for="f-start-allow">Start sessions' + localPillHtml({ machine: info.machine, emoji, esc }) + '</label>' +
262
+ '<label class="check' + (local ? '' : ' locked') + '"><input id="f-start-allow" type="checkbox"' + (info.allowRemote ? ' checked' : '') + lockedAttrs(local) + '>' + lockHtml(local, icon) + '<span>Allow starting sessions from other devices</span></label>' +
263
+ '<div class="hint">Off: a session your phone or another Mac starts here waits until someone at this Mac confirms it. On: it starts right away, and this Mac shows a notification each time. Needs Trust my paired devices.</div>' +
264
+ '<div class="hint local-hint">' + (local ? 'Or run <code>leftoff starts on</code> on this Mac.' : 'Change on ' + m + ', or run <code>leftoff starts on</code> there.') + '</div>'
216
265
  }