experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(cmd-panel): migrate to api.izui + replace api.modes calls; surface 'open <noun>' variants in tab cycle

+33 -36
+33 -36
app/cmd/panel.js
··· 237 237 238 238 const loadCommandContext = async () => { 239 239 try { 240 - const isTransient = await api.window.isTransient(); 240 + const isTransient = await api.izui.isTransient(); 241 241 if (isTransient) { 242 242 log('cmd:panel', 'Transient invocation - using default mode'); 243 243 currentMode = 'default'; 244 244 currentModeMetadata = {}; 245 245 modeWasSet = true; 246 246 updateModeIndicator(); 247 - const result = await api.modes.getCommandContext(); 248 - if (result.success) { 249 - commandContext = result.data; 250 - } 247 + commandContext = { mode: { major: 'default' } }; 251 248 return; 252 249 } 253 250 ··· 264 261 } 265 262 } 266 263 267 - const result = await api.modes.getCommandContext(); 268 - if (result.success) { 269 - commandContext = result.data; 270 - log('cmd:panel', 'Loaded command context:', commandContext); 271 - if (!modeWasSet && commandContext?.mode?.major) { 272 - currentMode = commandContext.mode.major; 273 - modeWasSet = true; 274 - updateModeIndicator(); 275 - } 276 - } 264 + // Construct commandContext from the resolved mode. The legacy 265 + // api.modes.getCommandContext() returned a richer shape, but the 266 + // only field consumed downstream (isCommandAvailable) is 267 + // commandContext.mode.major — derive it from currentMode. 268 + commandContext = { mode: { major: currentMode || 'default' } }; 269 + log('cmd:panel', 'Loaded command context:', commandContext); 277 270 } catch (err) { 278 271 log.error('cmd:panel', 'Failed to load command context:', err); 279 272 commandContext = null; ··· 287 280 288 281 log('cmd:panel', 'Cycling mode from', currentMode, 'to', nextMode); 289 282 290 - if (api.context) { 291 - const targetWindowId = await api.window.getFocusedVisibleWindowId(); 292 - const result = await api.context.setMode(nextMode, { windowId: targetWindowId }); 293 - if (result.success) { 294 - currentMode = nextMode; 295 - currentModeMetadata = {}; 296 - updateModeIndicator(); 297 - await loadCommandContext(); 298 - return; 299 - } 300 - } 301 - 302 - const result = await api.modes.setMajorMode(nextMode); 283 + const targetWindowId = await api.window.getFocusedVisibleWindowId(); 284 + const result = await api.context.setMode(nextMode, { windowId: targetWindowId }); 303 285 if (result.success) { 304 286 currentMode = nextMode; 305 287 currentModeMetadata = {}; ··· 348 330 }); 349 331 } 350 332 351 - api.modes.onModeChange((modeState, windowId) => { 352 - log('cmd:panel', 'Mode changed (modes API):', modeState, 'for window:', windowId); 353 - if (!currentMode || currentMode === 'default') { 354 - currentMode = modeState.major || 'default'; 355 - updateModeIndicator(); 356 - } 357 - }); 358 - 359 333 log('cmd:panel', 'Mode indicator initialized with mode:', currentMode); 360 334 } 361 335 ··· 412 386 } 413 387 } 414 388 389 + // Token-prefix predicate: returns true if input is a prefix of any 390 + // whitespace-separated token in the command name. This brings 391 + // multi-word variants (e.g. "open tags") into the same rank tier as 392 + // the bare noun ("tags") for input "tag" — otherwise the multi-word 393 + // variant has zero frecency and falls past the end of a Tab cycle 394 + // the user never reaches. 395 + const tokenPrefixMatch = (name) => { 396 + const tokens = name.toLowerCase().split(/\s+/); 397 + for (const tok of tokens) { 398 + if (tok.startsWith(lowerCommandPart)) return true; 399 + } 400 + return false; 401 + }; 402 + 415 403 // Sort by combined ranking score 416 404 matches.sort(function(a, b) { 417 405 const aLower = a.toLowerCase(); ··· 423 411 if (aExact && !bExact) return -1; 424 412 if (bExact && !aExact) return 1; 425 413 } 414 + 415 + // Token-prefix tier: any command where the input is a prefix of 416 + // some token outranks pure substring-only matches. This keeps 417 + // bare-noun ("tags") and "open <noun>" ("open tags") adjacent in 418 + // the cycle for input "tag". 419 + const aTokenPrefix = tokenPrefixMatch(a); 420 + const bTokenPrefix = tokenPrefixMatch(b); 421 + if (aTokenPrefix && !bTokenPrefix) return -1; 422 + if (bTokenPrefix && !aTokenPrefix) return 1; 426 423 427 424 const aAdaptive = getAdaptiveScore(commandPart, a); 428 425 const bAdaptive = getAdaptiveScore(commandPart, b);